diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 6dbc1c6113..5f3983c7f5 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -22,10 +22,8 @@ jobs: key: ${{ runner.os }}-maven-build-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven-build- - - name: Maven Package - run: mvn -B clean package -DskipTests - name: Maven Install - run: mvn -B install + run: mvn -B clean install - name: Code coverage report uses: codecov/codecov-action@v1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2260a5bbb..f864cb1cd6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,6 +78,8 @@ jobs: sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" $file || echo "File $file hasn't been updated!" cp diktat-rules/src/main/resources/diktat-analysis.yml $(dirname $file) done + next_snapshot_version=$(printf 'VERSION=${project.version}\n0\n' | mvn help:evaluate | grep '^VERSION' | cut -d= -f2) + echo "version=$next_snapshot_version" > info/buildSrc/gradle.properties - name: Create pull request uses: peter-evans/create-pull-request@v3 with: diff --git a/README.md b/README.md index 314ee429e7..0f3a8f5295 100644 --- a/README.md +++ b/README.md @@ -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) | +| | | | | | +| --- | --- | --- | --- | --- | +|[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? diff --git a/diktat-analysis.yml b/diktat-analysis.yml index d6f4e18929..af4695f9e4 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 \ No newline at end of file diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index 08f576145d..5bf9b08466 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -20,7 +20,7 @@ repositories { // default value is needed for correct gradle loading in IDEA; actual value from maven is used during build val ktlintVersion = project.properties.getOrDefault("ktlintVersion", "0.39.0") as String -val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.2.0" +val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.3.0" val junitVersion = project.properties.getOrDefault("junitVersion", "5.7.0") as String val jacocoVersion = project.properties.getOrDefault("jacocoVersion", "0.8.6") as String dependencies { @@ -98,6 +98,7 @@ tasks.getByName("functionalTest") { dependsOn("test") testClassesDirs = functionalTest.output.classesDirs classpath = functionalTest.runtimeClasspath + maxParallelForks = Runtime.getRuntime().availableProcessors() doLast { if (getCurrentOperatingSystem().isWindows) { // workaround for https://github.com/koral--/jacoco-gradle-testkit-plugin/issues/9 diff --git a/diktat-rules/pom.xml b/diktat-rules/pom.xml index 2c166e9909..280f6d6ceb 100644 --- a/diktat-rules/pom.xml +++ b/diktat-rules/pom.xml @@ -110,6 +110,7 @@ + src/main/kotlin src/test/kotlin ${project.basedir}/src/main/kotlin diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 10842cf258..5332a3e099 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -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" @@ -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" @@ -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" @@ -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" } 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 156d662dae..e8e088167b 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 @@ -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"), @@ -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"), @@ -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"), @@ -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"), ; /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/generation/Generation.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/generation/Generation.kt index 93cd94ddbb..946de2a73a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/generation/Generation.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/generation/Generation.kt @@ -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) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt index b337dd9520..eccc3ab41c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt @@ -68,7 +68,12 @@ class AnnotationNewLineRule(private val configRules: List) : 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") } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AsyncAndSyncRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AsyncAndSyncRule.kt new file mode 100644 index 0000000000..7f7f08f7c0 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AsyncAndSyncRule.kt @@ -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) : 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) +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt index acf64f92ac..e381be9235 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt @@ -221,14 +221,14 @@ class BlockStructureBraces(private val configRules: List) : Rule("b allMiddleSpace: List, 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(" ") } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/CheckInverseMethodRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/CheckInverseMethodRule.kt new file mode 100644 index 0000000000..2f11475454 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/CheckInverseMethodRule.kt @@ -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) : 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) + } +} 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 5fc4b700b2..60aba56ab6 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 @@ -111,6 +111,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::CustomGetterSetterRule, ::CompactInitialization, // other rules + ::CheckInverseMethodRule, ::StatelessClassesRule, ::ImplicitBackingPropertyRule, ::StringTemplateFormatRule, @@ -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, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt index 82d3f02592..a9c619fae2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt @@ -43,11 +43,10 @@ class FunctionLength(private val configRules: List) : Rule("functio ?.node ?.clone() ?: return) as ASTNode } - copyNode.findAllNodesWithCondition({ it.elementType in commentType }).forEach { it.treeParent.removeChild(it) } - val functionText = copyNode.text.lines().filter { it.isNotBlank() } - if (functionText.size > configuration.maxFunctionLength) { + val sizeFun = countCodeLines(copyNode) + if (sizeFun > configuration.maxFunctionLength) { TOO_LONG_FUNCTION.warn(configRules, emitWarn, isFixMode, - "max length is ${configuration.maxFunctionLength}, but you have ${functionText.size}", + "max length is ${configuration.maxFunctionLength}, but you have $sizeFun", node.startOffset, node) } } 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 cb37fd2937..a393f46bcc 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 @@ -451,7 +451,7 @@ class IdentifierNaming(private val configRules: List) : Rule("ident // FixMe: this should be moved to properties val oneCharIdentifiers = setOf("i", "j", "k", "x", "y", "z") - val booleanMethodPrefixes = setOf("has", "is", "are", "have", "should") + val booleanMethodPrefixes = setOf("has", "is", "are", "have", "should", "can") val confusingIdentifierNames = setOf("O", "D", "I", "l", "Z", "S", "e", "B", "h", "n", "m", "rn") } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LambdaLengthRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LambdaLengthRule.kt new file mode 100644 index 0000000000..681b9bd077 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LambdaLengthRule.kt @@ -0,0 +1,71 @@ +package org.cqfn.diktat.ruleset.rules + +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.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.utils.* + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** + * Rule 5.2.5 check lambda length without parameters + */ +class LambdaLengthRule(private val configRules: List) : Rule("lambda-length") { + private val configuration by lazy { + LambdaLengthConfiguration( + this.configRules.getRuleConfig(Warnings.TOO_MANY_LINES_IN_LAMBDA)?.configuration ?: emptyMap() + ) + } + 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 == ElementType.LAMBDA_EXPRESSION) { + checkLambda(node, configuration) + } + } + + private fun checkLambda(node: ASTNode, configuration: LambdaLengthConfiguration) { + val copyNode = node.clone() as ASTNode + val sizeLambda = countCodeLines(copyNode) + if (sizeLambda > configuration.maxLambdaLength) { + copyNode.findAllNodesWithCondition({ it.elementType == ElementType.LAMBDA_EXPRESSION }).forEachIndexed { index, node -> + if (index > 0) { + node.treeParent.removeChild(node) + } + } + val isIt = copyNode.findAllNodesWithSpecificType(ElementType.REFERENCE_EXPRESSION).map {re -> re.text}.contains("it") + val parameters = node.findChildByType(ElementType.FUNCTION_LITERAL)?.findChildByType(ElementType.VALUE_PARAMETER_LIST) + if (parameters == null && isIt) { + Warnings.TOO_MANY_LINES_IN_LAMBDA.warn(configRules, emitWarn, isFixMode, + "max length lambda without arguments is ${configuration.maxLambdaLength}, but you have $sizeLambda", + node.startOffset, node) + } + } + } + + /** + * [RuleConfiguration] for lambda length + */ + class LambdaLengthConfiguration(config: Map) : RuleConfiguration(config) { + /** + * Maximum allowed lambda length + */ + val maxLambdaLength = config["maxLambdaLength"]?.toLong() ?: MAX_LINES_IN_LAMBDA + } + + companion object { + private const val MAX_LINES_IN_LAMBDA = 10L + } +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt index f7685c1e2b..e6ad7f6dbb 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt @@ -84,21 +84,21 @@ class LineLength(private val configRules: List) : Rule("line-length @Suppress("UnsafeCallOnNullableType") private fun checkLength(node: ASTNode, configuration: LineLengthConfiguration) { var offset = 0 - node.text.lines().forEach { - if (it.length > configuration.lineLength) { + node.text.lines().forEach { line -> + if (line.length > configuration.lineLength) { val newNode = node.psi.findElementAt(offset + configuration.lineLength.toInt())!!.node if ((newNode.elementType != KDOC_TEXT && newNode.elementType != KDOC_MARKDOWN_INLINE_LINK) || !isKdocValid(newNode)) { positionByOffset = node.treeParent.calculateLineColByOffset() val fixableType = isFixable(newNode, configuration) LONG_LINE.warnAndFix(configRules, emitWarn, isFixMode, - "max line length ${configuration.lineLength}, but was ${it.length}", + "max line length ${configuration.lineLength}, but was ${line.length}", offset + node.startOffset, node, fixableType != LongLineFixableCases.None) { fixError(fixableType) } } } - offset += it.length + 1 + offset += line.length + 1 } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt index 1a58810826..c1c6ba14e4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt @@ -59,19 +59,19 @@ class MultipleModifiersSequence(private val configRules: List) : Ru node .getChildren(null) .filterIndexed { index, astNode -> astNode.elementType == ANNOTATION_ENTRY && index > firstModifierIndex } - .forEach { + .forEach { astNode -> WRONG_MULTIPLE_MODIFIERS_ORDER.warnAndFix(configRules, emitWarn, isFixMode, - "${it.text} annotation should be before all modifiers", - it.startOffset, it) { - val spaceBefore = it.treePrev - node.removeChild(it) + "${astNode.text} annotation should be before all modifiers", + astNode.startOffset, astNode) { + val spaceBefore = astNode.treePrev + node.removeChild(astNode) if (spaceBefore != null && spaceBefore.elementType == WHITE_SPACE) { node.removeChild(spaceBefore) node.addChild(spaceBefore, node.firstChildNode) - node.addChild(it.clone() as ASTNode, spaceBefore) + node.addChild(astNode.clone() as ASTNode, spaceBefore) } else { node.addChild(PsiWhiteSpaceImpl(" "), node.getChildren(null).first()) - node.addChild(it.clone() as ASTNode, node.getChildren(null).first()) + node.addChild(astNode.clone() as ASTNode, node.getChildren(null).first()) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt index 61c8bb9996..57438aec78 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt @@ -160,9 +160,8 @@ class NullChecksRule(private val configRules: List) : Rule("null-ch } } - @Suppress("WRONG_INDENTATION") private fun ASTNode.extractLinesFromBlock(type: IElementType): List? = - treeParent + treeParent .getFirstChildWithType(type) ?.text ?.trim('{', '}') diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullableTypeRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullableTypeRule.kt index 6565093846..1317fdd8e5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullableTypeRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullableTypeRule.kt @@ -80,7 +80,10 @@ class NullableTypeRule(private val configRules: List) : Rule("nulla @Suppress("UnsafeCallOnNullableType") private fun findFixableParam(node: ASTNode): FixedParam? { - val reference = node.findChildByType(TYPE_REFERENCE)!!.findChildByType(NULLABLE_TYPE)!!.findChildByType(USER_TYPE)?.findChildByType(REFERENCE_EXPRESSION) + val reference = node.findChildByType(TYPE_REFERENCE)!! + .findChildByType(NULLABLE_TYPE)!! + .findChildByType(USER_TYPE) + ?.findChildByType(REFERENCE_EXPRESSION) ?: return null return when (reference.text) { "Boolean" -> FixedParam(BOOLEAN_CONSTANT, TRUE_KEYWORD, "true") diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt index 439dc40442..b1a3e3bd4c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt @@ -33,18 +33,18 @@ class SingleLineStatementsRule(private val configRules: List) : Rul } private fun checkSemicolon(node: ASTNode) { - node.getChildren(semicolonToken).forEach { - if (!it.isFollowedByNewline()) { - MORE_THAN_ONE_STATEMENT_PER_LINE.warnAndFix(configRules, emitWarn, isFixMode, it.extractLineOfText(), - it.startOffset, it) { - if (it.treeParent.elementType == ENUM_ENTRY) { + node.getChildren(semicolonToken).forEach { astNode -> + if (!astNode.isFollowedByNewline()) { + MORE_THAN_ONE_STATEMENT_PER_LINE.warnAndFix(configRules, emitWarn, isFixMode, astNode.extractLineOfText(), + astNode.startOffset, astNode) { + if (astNode.treeParent.elementType == ENUM_ENTRY) { node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) } else { - if (!it.isBeginByNewline()) { - val nextNode = it.parent({ parent -> parent.treeNext != null }, strict = false)?.treeNext - node.appendNewlineMergingWhiteSpace(nextNode, it) + if (!astNode.isBeginByNewline()) { + val nextNode = astNode.parent({ parent -> parent.treeNext != null }, strict = false)?.treeNext + node.appendNewlineMergingWhiteSpace(nextNode, astNode) } - node.removeChild(it) + node.removeChild(astNode) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt index ad4a3fd533..9e5b3c07c0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt @@ -90,15 +90,16 @@ class SmartCastRule(private val configRules: List) : Rule("smart-ca */ @Suppress("NestedBlockDepth", "TYPE_ALIAS") private fun handleGroups(groups: Map>) { - groups.keys.forEach { - if (it.node.treeParent.text.contains(" is ")) { - groups.getValue(it).forEach { asCall -> + groups.keys.forEach { key -> + val parentText = key.node.treeParent.text + if (parentText.contains(" is ")) { + groups.getValue(key).forEach { asCall -> if (asCall.node.hasParent(THEN)) { raiseWarning(asCall.node) } } - } else if (it.node.treeParent.text.contains(" !is ")) { - groups.getValue(it).forEach { asCall -> + } else if (parentText.contains(" !is ")) { + groups.getValue(key).forEach { asCall -> if (asCall.node.hasParent(ELSE)) { raiseWarning(asCall.node) } @@ -233,12 +234,12 @@ class SmartCastRule(private val configRules: List) : Rule("smart-ca val identifier = node.getFirstChildWithType(REFERENCE_EXPRESSION)?.text - node.getAllChildrenWithType(WHEN_ENTRY).forEach { - if (it.hasChildOfType(WHEN_CONDITION_IS_PATTERN) && identifier != null) { - val type = it.getFirstChildWithType(WHEN_CONDITION_IS_PATTERN)!! + node.getAllChildrenWithType(WHEN_ENTRY).forEach { entry -> + if (entry.hasChildOfType(WHEN_CONDITION_IS_PATTERN) && identifier != null) { + val type = entry.getFirstChildWithType(WHEN_CONDITION_IS_PATTERN)!! .getFirstChildWithType(TYPE_REFERENCE)?.text - val callExpr = it.findAllNodesWithSpecificType(BINARY_WITH_TYPE).firstOrNull() + val callExpr = entry.findAllNodesWithSpecificType(BINARY_WITH_TYPE).firstOrNull() val blocks = listOf(IsExpressions(identifier, type ?: "")) callExpr?.let { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt index ade3e358e9..4a15f4ad12 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt @@ -106,11 +106,11 @@ class SingleInitRule(private val configRule: List) : Rule("multiple !(property.psi as KtProperty).hasBody() && assignments.size == 1 } .takeIf { it.isNotEmpty() } - ?.let { + ?.let { map -> Warnings.MULTIPLE_INIT_BLOCKS.warnAndFix(configRule, emitWarn, isFixMode, "`init` block has assignments that can be moved to declarations", initBlock.startOffset, initBlock ) { - it.forEach { (property, assignments) -> + map.forEach { (property, assignments) -> val assignment = assignments.single() property.addChild(PsiWhiteSpaceImpl(" "), null) property.addChild(LeafPsiElement(EQ, "="), null) 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 d31bc93ee3..2f799d34a3 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 @@ -4,17 +4,19 @@ 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.ruleset.constants.EmitType -import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_CONTAINS_DATE_OR_AUTHOR import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_MISSING_OR_WRONG_COPYRIGHT import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_NOT_BEFORE_PACKAGE import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_WRONG_FORMAT +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_COPYRIGHT_YEAR import org.cqfn.diktat.ruleset.utils.copyrightWords import org.cqfn.diktat.ruleset.utils.findChildAfter import org.cqfn.diktat.ruleset.utils.findChildBefore import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.kDocTags +import org.cqfn.diktat.ruleset.utils.log import org.cqfn.diktat.ruleset.utils.moveChildBefore import com.pinterest.ktlint.core.Rule @@ -29,8 +31,12 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement 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.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoField /** * Visitor for header comment in .kt file: @@ -59,18 +65,7 @@ class HeaderCommentRule(private val configRules: List) : Rule("head } private fun checkHeaderKdoc(node: ASTNode) { - val headerKdoc = node.findChildBefore(PACKAGE_DIRECTIVE, KDOC) - headerKdoc?.let { - // 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(configRules, emitWarn, isFixMode, - it.trim(), headerKdoc.startOffset, headerKdoc) - } - + node.findChildBefore(PACKAGE_DIRECTIVE, KDOC)?.let { headerKdoc -> if (headerKdoc.treeNext != null && headerKdoc.treeNext.elementType == WHITE_SPACE && headerKdoc.treeNext.text.count { it == '\n' } != 2) { HEADER_WRONG_FORMAT.warnAndFix(configRules, emitWarn, isFixMode, @@ -148,15 +143,24 @@ class HeaderCommentRule(private val configRules: List) : Rule("head return } + if (makeCopyrightCorrectYear(configuration.getCopyrightText()).isNotEmpty()) { + log.warn("Copyright year is not up do date.") + } + + // need to make sure that copyright year is consistent with current year val copyrightText = configuration.getCopyrightText() val headerComment = node.findChildBefore(PACKAGE_DIRECTIVE, BLOCK_COMMENT) - val isWrongCopyright = headerComment != null && !headerComment.text.flatten().contains(copyrightText.flatten()) + // Depends only on content and doesn't consider years + val isWrongCopyright = headerComment != null && + !headerComment.text.flatten().contains(copyrightText.flatten()) && + !headerComment.text.flatten().contains(makeCopyrightCorrectYear(copyrightText).flatten()) val isMissingCopyright = headerComment == null && configuration.isCopyrightMandatory() val isCopyrightInsideKdoc = (node.getAllChildrenWithType(KDOC) + node.getAllChildrenWithType(ElementType.EOL_COMMENT)) .any { commentNode -> copyrightWords.any { commentNode.text.contains(it, ignoreCase = true) } } + if (isWrongCopyright || isMissingCopyright || isCopyrightInsideKdoc) { val freeText = when { // If `isCopyrightInsideKdoc` then `isMissingCopyright` is true too, but warning text from `isCopyrightInsideKdoc` is preferable. @@ -183,7 +187,8 @@ class HeaderCommentRule(private val configRules: List) : Rule("head val copyrightWithCorrectYear = makeCopyrightCorrectYear(copyrightText) - if (copyrightWithCorrectYear.isNotEmpty()) { + // Triggers when there is a copyright, but its year is not updated. + if (!isMissingCopyright && copyrightWithCorrectYear.isNotEmpty()) { WRONG_COPYRIGHT_YEAR.warnAndFix(configRules, emitWarn, isFixMode, "year should be $curYear", node.startOffset, node) { (headerComment as LeafElement).replaceWithText(headerComment.text.replace(copyrightText, copyrightWithCorrectYear)) } @@ -239,7 +244,7 @@ class HeaderCommentRule(private val configRules: List) : Rule("head } companion object { - val hyphenRegex = Regex("""\b(\d+-\d+)\b""") + val hyphenRegex = Regex("""\d+-\d+""") val afterCopyrightRegex = Regex("""((©|\([cC]\))+ *\d+)""") val curYear = LocalDate.now().year } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt index d08f1f742b..c2e86a3767 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt @@ -5,6 +5,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.EmitType import org.cqfn.diktat.ruleset.constants.ListOfList +import org.cqfn.diktat.ruleset.constants.Warnings.COMPLEX_EXPRESSION import org.cqfn.diktat.ruleset.constants.Warnings.REDUNDANT_SEMICOLON import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NEWLINES import org.cqfn.diktat.ruleset.utils.* @@ -18,10 +19,10 @@ import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.core.ast.ElementType.CALLABLE_REFERENCE_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CLASS -import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY import com.pinterest.ktlint.core.ast.ElementType.COLON import com.pinterest.ktlint.core.ast.ElementType.COLONCOLON import com.pinterest.ktlint.core.ast.ElementType.COMMA +import com.pinterest.ktlint.core.ast.ElementType.CONDITION import com.pinterest.ktlint.core.ast.ElementType.DIV import com.pinterest.ktlint.core.ast.ElementType.DIVEQ import com.pinterest.ktlint.core.ast.ElementType.DOT @@ -51,7 +52,6 @@ import com.pinterest.ktlint.core.ast.ElementType.PLUS import com.pinterest.ktlint.core.ast.ElementType.PLUSEQ import com.pinterest.ktlint.core.ast.ElementType.POSTFIX_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR -import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.RETURN import com.pinterest.ktlint.core.ast.ElementType.RETURN_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS @@ -59,11 +59,12 @@ import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.SECONDARY_CONSTRUCTOR import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST +import com.pinterest.ktlint.core.ast.ElementType.WHEN import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE -import com.pinterest.ktlint.core.ast.isWhiteSpace import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline import com.pinterest.ktlint.core.ast.nextCodeSibling import com.pinterest.ktlint.core.ast.parent @@ -92,6 +93,7 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings * 7. Ensures that in multiline lambda newline follows arrow or, in case of lambda without explicit parameters, opening brace * 8. Checks that functions with single `return` are simplified to functions with expression body * 9. parameter or argument lists and supertype lists that have more than 2 elements should be separated by newlines + * 10. Complex expression inside condition replaced with new variable */ @Suppress("ForbiddenComment") class NewlinesRule(private val configRules: List) : Rule("newlines") { @@ -154,18 +156,24 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" } } - @Suppress("ComplexMethod") + @Suppress("ComplexMethod", "TOO_LONG_FUNCTION") private fun handleOperatorWithLineBreakBefore(node: ASTNode) { if (node.isDotFromPackageOrImport()) { return } val isIncorrect = (if (node.elementType == ELVIS) node.treeParent else node).run { if (isCallsChain()) { + if (node.isInParentheses()) { + COMPLEX_EXPRESSION.warn(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) + } val isSingleLineIfElse = parent({ it.elementType == IF }, true)?.isSingleLineIfElse() ?: false // to follow functional style these operators should be started by newline (isFollowedByNewline() || !isBeginByNewline()) && !isSingleLineIfElse && (!isFirstCall() || !isMultilineLambda(treeParent)) } else { + if (isCallsChain(false) && node.isInParentheses()) { + COMPLEX_EXPRESSION.warn(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) + } // unless statement is simple and on single line, these operators cannot have newline after isFollowedByNewline() && !isSingleDotStatementOnSingleLine() } @@ -375,11 +383,11 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" if (psi.children.isNotEmpty() && (!psi.isFirstChildElementType(DOT_QUALIFIED_EXPRESSION) && !psi.isFirstChildElementType(SAFE_ACCESS_EXPRESSION))) { val firstChild = psi.firstChild + if (firstChild.isFirstChildElementType(DOT_QUALIFIED_EXPRESSION) || + firstChild.isFirstChildElementType(SAFE_ACCESS_EXPRESSION)) { + getOrderedCallExpressions(firstChild.firstChild, result) + } if (firstChild.isFirstChildElementType(POSTFIX_EXPRESSION)) { - if (firstChild.isFirstChildElementType(DOT_QUALIFIED_EXPRESSION) || - firstChild.isFirstChildElementType(SAFE_ACCESS_EXPRESSION)) { - getOrderedCallExpressions(firstChild.firstChild, result) - } result.add(firstChild.node) } result.add(firstChild.node @@ -439,17 +447,23 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" * * @return true - if there is error, and false if there is no error */ - private fun ASTNode.isCallsChain() = getCallChain()?.isNotValidCalls(this) ?: false - - private fun ASTNode.getCallChain() = getParentExpressions() - .lastOrNull() - ?.run { - mutableListOf().also { - getOrderedCallExpressions(psi, it) + private fun ASTNode.isCallsChain(dropLeadingProperties: Boolean = true) = getCallChain(dropLeadingProperties)?.isNotValidCalls(this) ?: false + + private fun ASTNode.getCallChain(dropLeadingProperties: Boolean = true): List? { + val parentExpressionList = getParentExpressions() + .lastOrNull() + ?.run { + mutableListOf().also { + getOrderedCallExpressions(psi, it) + } } + return if (dropLeadingProperties) { + // fixme: we can't distinguish fully qualified names (like java.lang) from chain of property accesses (like list.size) for now + parentExpressionList?.dropWhile { !it.treeParent.textContains('(') && !it.treeParent.textContains('{') } + } else { + parentExpressionList } - // fixme: we can't distinguish fully qualified names (like java.lang) from chain of property accesses (like list.size) for now - ?.dropWhile { !it.treeParent.textContains('(') && !it.treeParent.textContains('{') } + } private fun List.isNotValidCalls(node: ASTNode): Boolean { if (this.size == 1) { @@ -457,15 +471,15 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" } val callsByNewLine: ListOfList = mutableListOf() var callsInOneNewLine: MutableList = mutableListOf() - this.forEach { - if (it.treePrev.isFollowedByNewline() || it.treePrev.isWhiteSpaceWithNewline()) { + this.forEach { node -> + if (node.treePrev.isFollowedByNewline() || node.treePrev.isWhiteSpaceWithNewline()) { callsByNewLine.add(callsInOneNewLine) callsInOneNewLine = mutableListOf() - callsInOneNewLine.add(it) + callsInOneNewLine.add(node) } else { - callsInOneNewLine.add(it) + callsInOneNewLine.add(node) } - if (it.treePrev.elementType == POSTFIX_EXPRESSION && !it.treePrev.isFollowedByNewline() && configuration.maxCallsInOneLine == 1) { + if (node.treePrev.elementType == POSTFIX_EXPRESSION && !node.treePrev.isFollowedByNewline() && configuration.maxCallsInOneLine == 1) { return true } } @@ -504,6 +518,15 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" firstChildNode.elementType == IDENTIFIER && treeParent.elementType == BINARY_EXPRESSION + /** + * This method checks that complex expression should be replace with new variable + */ + private fun ASTNode.isInParentheses() = parent({it.elementType == DOT_QUALIFIED_EXPRESSION || it.elementType == SAFE_ACCESS_EXPRESSION}) + ?.treeParent + ?.elementType + ?.let { it in parenthesesTypes } + ?: false + /** * [RuleConfiguration] for newlines placement */ @@ -527,5 +550,6 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" private val expressionTypes = TokenSet.create(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION, CALLABLE_REFERENCE_EXPRESSION, BINARY_EXPRESSION) private val chainExpressionTypes = TokenSet.create(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION) private val dropChainValues = TokenSet.create(EOL_COMMENT, WHITE_SPACE, BLOCK_COMMENT, KDOC) + private val parenthesesTypes = TokenSet.create(CONDITION, WHEN, VALUE_ARGUMENT) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt index dbcd5a8061..9c4676ccf7 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/identifiers/LocalVariablesRule.kt @@ -91,7 +91,7 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc val declarationScope = property.getDeclarationScope() val firstUsageStatementLine = getFirstUsageStatementOrBlock(usages, declarationScope).node.getLineNumber() - val firstUsage = usages.minBy { it.node.getLineNumber() }!! + val firstUsage = usages.minByOrNull { it.node.getLineNumber() }!! // should skip val and var before it's statement val offset = property @@ -184,7 +184,7 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc */ @Suppress("UnsafeCallOnNullableType", "GENERIC_VARIABLE_WRONG_DECLARATION") private fun getFirstUsageStatementOrBlock(usages: List, declarationScope: KtBlockExpression?): PsiElement { - val firstUsage = usages.minBy { it.node.getLineNumber() }!! + val firstUsage = usages.minByOrNull { it.node.getLineNumber() }!! val firstUsageScope = firstUsage.getParentOfType(true) return if (firstUsageScope == declarationScope) { @@ -194,7 +194,7 @@ class LocalVariablesRule(private val configRules: List) : Rule("loc .find { it.parent == declarationScope }!! } else { // first usage is in deeper block compared to declaration, need to check how close is declaration to the first line of the block - usages.minBy { it.node.getLineNumber() }!! + usages.minByOrNull { it.node.getLineNumber() }!! .parentsWithSelf .find { it.parent == declarationScope }!! } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt index 1696aa5e62..fefdfe34cc 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt @@ -1,7 +1,10 @@ package org.cqfn.diktat.ruleset.rules.kdoc +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.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_EMPTY_KDOC import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NEWLINES_BEFORE_BASIC_TAGS import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_DEPRECATED_TAG @@ -43,6 +46,9 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.psiUtil.startOffset +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoField + /** * Formatting visitor for Kdoc: * 1) removing all blank lines between Kdoc and the code it's declaring @@ -50,12 +56,15 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset * 3) ensuring there is only one white space between tag and it's body * 4) ensuring tags @apiNote, @implSpec, @implNote have one empty line after their body * 5) ensuring tags @param, @return, @throws are arranged in this order + * 6) ensuring @author tag is not used + * 7) ensuring @since tag contains only versions and not dates */ @Suppress("ForbiddenComment") class KdocFormatting(private val configRules: List) : Rule("kdoc-formatting") { private val basicTagsList = listOf(KDocKnownTag.PARAM, KDocKnownTag.RETURN, KDocKnownTag.THROWS) private val specialTagNames = setOf("implSpec", "implNote", "apiNote") private var isFixMode: Boolean = false + private var versionRegex: Regex? = null private lateinit var emitWarn: EmitType /** @@ -69,6 +78,13 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo isFixMode = autoCorrect emitWarn = emit + versionRegex ?: run { + versionRegex = KdocFormatConfiguration( + configRules.getRuleConfig(KDOC_CONTAINS_DATE_OR_AUTHOR)?.configuration ?: emptyMap() + ) + .versionRegex + } + if (node.elementType == KDOC && isKdocNotEmpty(node)) { checkNoDeprecatedTag(node) checkEmptyTags(node.kDocTags()) @@ -77,6 +93,7 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo node.kDocBasicTags()?.let { checkEmptyLinesBetweenBasicTags(it) } checkBasicTagsOrder(node) checkNewLineAfterSpecialTags(node) + checkAuthorAndDate(node) } } @@ -307,6 +324,17 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo } } + private fun checkAuthorAndDate(node: ASTNode) { + node.kDocTags() + ?.filter { + it.knownTag == KDocKnownTag.AUTHOR || + it.knownTag == KDocKnownTag.SINCE && it.hasInvalidVersion() + } + ?.forEach { + KDOC_CONTAINS_DATE_OR_AUTHOR.warn(configRules, emitWarn, isFixMode, it.text.trim(), it.startOffset, it.node) + } + } + // fixme this method can be improved and extracted to utils private fun ASTNode.hasEmptyLineAfter(): Boolean { require(this.elementType == KDOC_TAG) { "This check is only for KDOC_TAG" } @@ -321,4 +349,43 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo private fun ASTNode.applyToPrevSibling(elementType: IElementType, consumer: ASTNode.() -> Unit) { prevSibling { it.elementType == elementType }?.apply(consumer) } + + /** + * Checks whether this tag's content represents an invalid version + */ + private fun KDocTag.hasInvalidVersion(): Boolean { + val content = getContent().trim() + if (' ' in content || '/' in content) { + // Filter based on symbols that are not allowed in versions. Assuming that people put either version or date in `@since` tag. + return true + } + return versionRegex?.matches(content)?.not() + ?: dateFormats.mapNotNull { + // try to parse using some standard date patterns + runCatching { + it.parse(content).get(ChronoField.YEAR) + } + .getOrNull() + } + .isNotEmpty() + } + + /** + * A [RuleConfiguration] for KDoc formatting + */ + class KdocFormatConfiguration(config: Map) : RuleConfiguration(config) { + /** + * Regular expression, if present, against which a version should be matched in `@since` tag. + */ + val versionRegex: Regex? by lazy { + config["versionRegex"]?.let { Regex(it) } + } + } + + companion object { + val dateFormats: List = listOf("yyyy-dd-mm", "yy-dd-mm", "yyyy-mm-dd", "yy-mm-dd", "yyyy.mm.dd", "yyyy.dd.mm") + .map { + DateTimeFormatter.ofPattern(it) + } + } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt index 9acba46866..ebd6f425b7 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt @@ -603,7 +603,7 @@ fun ASTNode.isChildAfterAnother(child: ASTNode, afterChild: ASTNode): Boolean = * @return boolean result */ fun ASTNode.isChildAfterGroup(child: ASTNode, group: List): Boolean = - getChildren(null).indexOf(child) > (group.map { getChildren(null).indexOf(it) }.max() ?: 0) + getChildren(null).indexOf(child) > (group.map { getChildren(null).indexOf(it) }.maxOrNull() ?: 0) /** * Checks whether [child] is before [beforeChild] among the children of [this] node @@ -637,7 +637,7 @@ fun ASTNode.areChildrenBeforeChild(children: List, beforeChild: ASTNode @Suppress("UnsafeCallOnNullableType") fun ASTNode.areChildrenBeforeGroup(children: List, group: List): Boolean { require(children.isNotEmpty() && group.isNotEmpty()) { "no sense to operate on empty lists" } - return children.map { getChildren(null).indexOf(it) }.max()!! < group.map { getChildren(null).indexOf(it) }.min()!! + return children.map { getChildren(null).indexOf(it) }.maxOrNull()!! < group.map { getChildren(null).indexOf(it) }.minOrNull()!! } /** @@ -789,3 +789,14 @@ private fun ASTNode.calculateLineNumber() = getRootNode() require(it >= 0) { "Cannot calculate line number correctly, node's offset $startOffset is greater than file length ${getRootNode().textLength}" } it + 1 } + +/** + * Count number of lines in code block. Note: only *copy* of a node should be passed to this method, because the method changes the node. + * + * @return the number of lines in a block of code. + */ +fun countCodeLines(copyNode: ASTNode): Int { + copyNode.findAllNodesWithCondition({ it.isPartOfComment() }).forEach { it.treeParent.removeChild(it) } + val text = copyNode.text.lines().filter { it.isNotBlank() } + return text.size +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt index a3e907f4d9..775da020d2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt @@ -145,24 +145,24 @@ private fun convertUnknownCaseToCamel(str: String, isFirstLetterCapital: Boolean // [p]a[SC]a[_]l -> [P]a[Sc]a[L] var isPreviousLetterCapital = isFirstLetterCapital var isPreviousLetterUnderscore = false - return str.map { - if (it.isUpperCase()) { - val result = if (isPreviousLetterCapital && !isPreviousLetterUnderscore) it.toLowerCase() else it + return str.map { char -> + if (char.isUpperCase()) { + val result = if (isPreviousLetterCapital && !isPreviousLetterUnderscore) char.toLowerCase() else char isPreviousLetterCapital = true isPreviousLetterUnderscore = false result.toString() } else { - val result = if (it == '_') { + val result = if (char == '_') { isPreviousLetterUnderscore = true "" } else if (isPreviousLetterUnderscore) { isPreviousLetterCapital = true isPreviousLetterUnderscore = false - it.toUpperCase().toString() + char.toUpperCase().toString() } else { isPreviousLetterCapital = false isPreviousLetterUnderscore = false - it.toString() + char.toString() } result } @@ -172,17 +172,17 @@ private fun convertUnknownCaseToCamel(str: String, isFirstLetterCapital: Boolean private fun convertUnknownCaseToUpperSnake(str: String): String { // [p]a[SC]a[_]l -> [P]A_[SC]_A_[L] var alreadyInsertedUnderscore = true - return str.map { - if (it.isUpperCase()) { + return str.map { char -> + if (char.isUpperCase()) { if (!alreadyInsertedUnderscore) { alreadyInsertedUnderscore = true - "_$it" + "_$char" } else { - it.toString() + char.toString() } } else { - alreadyInsertedUnderscore = (it == '_') - it.toUpperCase().toString() + alreadyInsertedUnderscore = (char == '_') + char.toUpperCase().toString() } }.joinToString("") } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt index e873f1dd83..fbc1789e8d 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt @@ -110,5 +110,5 @@ abstract class VariablesSearch(val node: ASTNode, private val filterForVariables * * @param node an [ASTNode] */ -@SuppressWarnings("FunctionOnlyReturningConstant") +@Suppress("UNUSED_PARAMETER", "FunctionOnlyReturningConstant") fun default(node: KtProperty) = true diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 2681f665db..58c2966f91 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -112,6 +112,11 @@ # Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after - name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS 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 KDoc does not contain single line with words 'return', 'get' or 'set' - name: KDOC_TRIVIAL_KDOC_ON_FUNCTION enabled: true @@ -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 @@ -230,6 +232,9 @@ maxSpaces: '1' # Whether formatting for enums should be kept without checking saveInitialFormattingForEnums: false +# Inspection that checks if a long dot qualified expression is used in condition or as an argument +- name: COMPLEX_EXPRESSION + enabled: true # Checks that blank lines are used correctly. # For example: triggers when there are too many blank lines between function declaration - name: TOO_MANY_BLANK_LINES @@ -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 @@ -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 \ No newline at end of file diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml index cf353c0ce0..84f0a7275f 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -111,6 +111,11 @@ # Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after - name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS 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 KDoc does not contain single line with words 'return', 'get' or 'set' - name: KDOC_TRIVIAL_KDOC_ON_FUNCTION enabled: true @@ -129,9 +134,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 @@ -227,6 +229,9 @@ maxSpaces: '1' # Whether formatting for enums should be kept without checking saveInitialFormattingForEnums: false +# Inspection that checks if a long dot qualified expression is used in condition or as an argument +- name: COMPLEX_EXPRESSION + enabled: true # Checks that blank lines are used correctly. # For example: triggers when there are too many blank lines between function declaration - name: TOO_MANY_BLANK_LINES @@ -345,9 +350,17 @@ # 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 property in constructor doesn't contain comment - name: KDOC_NO_CONSTRUCTOR_PROPERTY 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 KDoc present in class - name: KDOC_EXTRA_PROPERTY enabled: true @@ -408,4 +421,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 \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt index 65628ffe74..d33e1c7440 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleFixTest.kt @@ -71,6 +71,17 @@ class HeaderCommentRuleFixTest : FixTestBase( ) } + @Test + @Tag(WRONG_COPYRIGHT_YEAR) + fun `should not raise npe`() { + fixAndCompare("CopyrightShouldNotTriggerNPEExpected.kt", "CopyrightShouldNotTriggerNPETest.kt", + listOf(RulesConfig(HEADER_MISSING_OR_WRONG_COPYRIGHT.name, true, mapOf( + "isCopyrightMandatory" to "true", + "copyrightText" to "Copyright (c) My Company., Ltd. 2012-2021. All rights reserved." + ))) + ) + } + @Test @Tag(WarningNames.HEADER_MISSING_OR_WRONG_COPYRIGHT) fun `copyright multiline`() { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt index a427a349f8..52493bbdfd 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/HeaderCommentRuleTest.kt @@ -2,11 +2,11 @@ package org.cqfn.diktat.ruleset.chapter2 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings -import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_CONTAINS_DATE_OR_AUTHOR import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_MISSING_OR_WRONG_COPYRIGHT import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_NOT_BEFORE_PACKAGE import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_WRONG_FORMAT +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.comments.HeaderCommentRule import org.cqfn.diktat.util.LintTestBase @@ -256,26 +256,6 @@ class HeaderCommentRuleTest : LintTestBase(::HeaderCommentRule) { ) } - @Test - @Tag(WarningNames.HEADER_CONTAINS_DATE_OR_AUTHOR) - fun `@author tag is not allowed in header comment`() { - lintMethod( - """ - |$copyrightBlock - |/** - | * Description of this file - | * @author anonymous - | */ - | - |package org.cqfn.diktat.example - | - |class Example { } - """.trimMargin(), - LintError(4, 1, ruleId, "${HEADER_CONTAINS_DATE_OR_AUTHOR.warnText()} * @author anonymous"), - rulesConfigList = rulesConfigList - ) - } - @Test @Tag(WarningNames.HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE) fun `file with zero classes should have header KDoc`() { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingTest.kt index 732797b566..02f2f092cb 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingTest.kt @@ -1,5 +1,7 @@ package org.cqfn.diktat.ruleset.chapter2 +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_EMPTY_KDOC import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NEWLINES_BEFORE_BASIC_TAGS import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_DEPRECATED_TAG @@ -321,4 +323,99 @@ class KdocFormattingTest : LintTestBase(::KdocFormatting) { LintError(2, 16, ruleId, "${KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnText()} @implSpec, @apiNote, @implNote", true)) } + + @Test + @Tag(WarningNames.KDOC_CONTAINS_DATE_OR_AUTHOR) + fun `@author tag is not allowed in header comment`() { + lintMethod( + """ + |/** + | * Description of this file + | * @author anonymous + | */ + | + |package org.cqfn.diktat.example + | + |/** + | * Description of this class + | * @author anonymous + | */ + |class Example { } + """.trimMargin(), + LintError(3, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @author anonymous"), + LintError(10, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @author anonymous"), + ) + } + + @Test + @Tag(WarningNames.KDOC_CONTAINS_DATE_OR_AUTHOR) + fun `@since tag should only contain versions`() { + lintMethod( + """ + |/** + | * Description of this file + | * @since 2019-10-11 + | * @since 19-10-11 + | * @since 2019.10.11 + | * @since 2019/10/11 + | * @since 11 Oct 2019 + | * @since 1.2.3 + | * @since 1.2.3-1 + | * @since 1.2.3-SNAPSHOT + | * @since 1.2.3-rc-1 + | * @since 1.2.3.RELEASE + | */ + | + |package org.cqfn.diktat.example + | + |/** + | * Description of this file + | * @since 2019-10-11 + | * @since 1.2.3 + | */ + |class Example { } + """.trimMargin(), + LintError(3, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 2019-10-11"), + LintError(4, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 19-10-11"), + LintError(5, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 2019.10.11"), + LintError(6, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 2019/10/11"), + LintError(7, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 11 Oct 2019"), + LintError(19, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 2019-10-11"), + rulesConfigList = emptyList() + ) + } + + @Test + @Tag(WarningNames.KDOC_CONTAINS_DATE_OR_AUTHOR) + fun `@since tag should only contain versions - with configured regex`() { + lintMethod( + """ + |/** + | * Description of this file + | * @since 2019-10-11 + | * @since 1.2.3-rc-1 + | * @since 1.2.3.RELEASE + | */ + | + |package org.cqfn.diktat.example + | + |/** + | * Description of this file + | * @since 2019-10-11 + | * @since 1.2.3 + | */ + |class Example { } + """.trimMargin(), + LintError(3, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 2019-10-11"), + LintError(5, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 1.2.3.RELEASE"), + LintError(12, 4, ruleId, "${Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.warnText()} @since 2019-10-11"), + rulesConfigList = listOf( + RulesConfig( + Warnings.KDOC_CONTAINS_DATE_OR_AUTHOR.name, true, mapOf( + "versionRegex" to "\\d+\\.\\d+\\.\\d+[-\\w\\d]*" + ) + ) + ) + ) + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt index 05878c2f7c..3d95d8603d 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt @@ -1,6 +1,7 @@ package org.cqfn.diktat.ruleset.chapter3.files import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings.COMPLEX_EXPRESSION import org.cqfn.diktat.ruleset.constants.Warnings.REDUNDANT_SEMICOLON import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NEWLINES import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID @@ -11,6 +12,7 @@ import com.pinterest.ktlint.core.LintError import generated.WarningNames import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Test @Suppress("LargeClass") @@ -905,6 +907,22 @@ class NewlinesRuleWarnTest : LintTestBase(::NewlinesRule) { ) } + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `test for not first prefix`() { + lintMethod( + """ + |fun foo() { + | a().b!!.c() + | a().b.c()!! + |} + """.trimMargin(), + LintError(2, 11, ruleId, "$functionalStyleWarn .", true), + LintError(3, 9, ruleId, "$functionalStyleWarn .", true), + rulesConfigList = rulesConfigListShort + ) + } + @Test @Tag(WarningNames.WRONG_NEWLINES) fun `test for null safety several lines`() { @@ -955,4 +973,39 @@ class NewlinesRuleWarnTest : LintTestBase(::NewlinesRule) { rulesConfigList = rulesConfigListShort ) } + + @Test + @Tags(Tag(WarningNames.WRONG_NEWLINES), Tag(WarningNames.COMPLEX_EXPRESSION)) + fun `complex expression in condition`() { + lintMethod( + """ + |fun foo() { + | if(a.b.c) {} + | while(a?.b?.c) {} + | when(a.b!!.c) {} + | goo(a?.b.c) + |} + """.trimMargin(), + LintError(2, 10, ruleId, "${COMPLEX_EXPRESSION.warnText()} .", false), + LintError(3, 14, ruleId, "${COMPLEX_EXPRESSION.warnText()} ?.", false), + LintError(4, 14, ruleId, "${COMPLEX_EXPRESSION.warnText()} .", false), + LintError(5, 12, ruleId, "${COMPLEX_EXPRESSION.warnText()} .", false), + rulesConfigList = rulesConfigListShort + ) + } + + @Test + @Tags(Tag(WarningNames.WRONG_NEWLINES), Tag(WarningNames.COMPLEX_EXPRESSION)) + fun `complex expression in condition with double warnings`() { + lintMethod( + """ + |fun foo() { + | if(a().b().c()) {} + |} + """.trimMargin(), + LintError(2, 14, ruleId, "${COMPLEX_EXPRESSION.warnText()} .", false), + LintError(2, 14, ruleId, "$functionalStyleWarn .", true), + rulesConfigList = rulesConfigListShort + ) + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/WhiteSpaceRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/WhiteSpaceRuleWarnTest.kt index ad68718a6b..04e850c62f 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/WhiteSpaceRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/WhiteSpaceRuleWarnTest.kt @@ -351,7 +351,7 @@ class WhiteSpaceRuleWarnTest : LintTestBase(::WhiteSpaceRule) { @Test @Tag(WarningNames.WRONG_WHITESPACE) - fun `there should be no space before ? in nullable types`() { + fun `there should be no space before question mark in nullable types`() { lintMethod( """ |class Example { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/AsyncAndSyncRuleTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/AsyncAndSyncRuleTest.kt new file mode 100644 index 0000000000..8d904c8ea1 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/AsyncAndSyncRuleTest.kt @@ -0,0 +1,74 @@ +package org.cqfn.diktat.ruleset.chapter5 + +import org.cqfn.diktat.ruleset.constants.Warnings.RUN_BLOCKING_INSIDE_ASYNC +import org.cqfn.diktat.ruleset.rules.AsyncAndSyncRule +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class AsyncAndSyncRuleTest : LintTestBase(::AsyncAndSyncRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:sync-in-async" + + @Test + @Tag(WarningNames.RUN_BLOCKING_INSIDE_ASYNC) + fun `test wrong case`() { + lintMethod( + """ + |fun foo() { + | GlobalScope.launch { + | c.addAndGet(i) + | } + | + | GlobalScope.async { + | n + | } + | + | GlobalScope.async { + | runBlocking { + | + | } + | } + |} + | + |suspend fun foo() { + | runBlocking { + | delay(2000) + | } + |} + | + """.trimMargin(), + LintError(11, 8, ruleId, "${RUN_BLOCKING_INSIDE_ASYNC.warnText()} runBlocking", false), + LintError(18, 4, ruleId, "${RUN_BLOCKING_INSIDE_ASYNC.warnText()} runBlocking", false) + ) + } + + @Test + @Tag(WarningNames.RUN_BLOCKING_INSIDE_ASYNC) + fun `test dot qualified expression case`() { + lintMethod( + """ + |fun foo() { + | GlobalScope.async { + | node.runBlocking() + | runBlocking { + | n++ + | } + | } + |} + | + |fun goo() { + | runBlocking { + | GlobalScope.async { + | n++ + | } + | } + |} + """.trimMargin(), + LintError(4, 8, ruleId, "${RUN_BLOCKING_INSIDE_ASYNC.warnText()} runBlocking", false) + ) + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/CheckInverseMethodRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/CheckInverseMethodRuleFixTest.kt new file mode 100644 index 0000000000..58d7f86c7b --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/CheckInverseMethodRuleFixTest.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat.ruleset.chapter5 + +import org.cqfn.diktat.ruleset.rules.CheckInverseMethodRule +import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames.INVERSE_FUNCTION_PREFERRED +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class CheckInverseMethodRuleFixTest : FixTestBase("test/paragraph5/method_call_names", ::CheckInverseMethodRule) { + @Test + @Tag(INVERSE_FUNCTION_PREFERRED) + fun `should fix method calls`() { + fixAndCompare("ReplaceMethodCallNamesExpected.kt", "ReplaceMethodCallNamesTest.kt") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/CheckInverseMethodRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/CheckInverseMethodRuleWarnTest.kt new file mode 100644 index 0000000000..924fd8f009 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/CheckInverseMethodRuleWarnTest.kt @@ -0,0 +1,74 @@ +package org.cqfn.diktat.ruleset.chapter5 + +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.CheckInverseMethodRule +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames.INVERSE_FUNCTION_PREFERRED +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class CheckInverseMethodRuleWarnTest : LintTestBase(::CheckInverseMethodRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:inverse-method" + + @Test + @Tag(INVERSE_FUNCTION_PREFERRED) + fun `should not raise warning`() { + lintMethod( + """ + |fun some() { + | if (list.isEmpty()) { + | // some cool logic + | } + |} + """.trimMargin() + ) + } + + @Test + @Tag(INVERSE_FUNCTION_PREFERRED) + fun `should raise warning`() { + lintMethod( + """ + |fun some() { + | if (!list.isEmpty()) { + | // some cool logic + | } + |} + """.trimMargin(), + LintError(2, 14, ruleId, "${Warnings.INVERSE_FUNCTION_PREFERRED.warnText()} isNotEmpty() instead of !isEmpty()", true) + ) + } + + @Test + @Tag(INVERSE_FUNCTION_PREFERRED) + fun `should consider white spaces between ! and call expression`() { + lintMethod( + """ + |fun some() { + | if (! list.isEmpty()) { + | // some cool logic + | } + |} + """.trimMargin(), + LintError(2, 16, ruleId, "${Warnings.INVERSE_FUNCTION_PREFERRED.warnText()} isNotEmpty() instead of !isEmpty()", true) + ) + } + + @Test + @Tag(INVERSE_FUNCTION_PREFERRED) + fun `should consider comments between ! and call expression`() { + lintMethod( + """ + |fun some() { + | if (! /*cool comment*/ list.isEmpty()) { + | // some cool logic + | } + |} + """.trimMargin(), + LintError(2, 32, ruleId, "${Warnings.INVERSE_FUNCTION_PREFERRED.warnText()} isNotEmpty() instead of !isEmpty()", true) + ) + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/LambdaLengthWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/LambdaLengthWarnTest.kt new file mode 100644 index 0000000000..5280bf48d8 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/LambdaLengthWarnTest.kt @@ -0,0 +1,175 @@ +package org.cqfn.diktat.ruleset.chapter5 + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.LambdaLengthRule +import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class LambdaLengthWarnTest : LintTestBase(::LambdaLengthRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:lambda-length" + private val rulesConfigList: List = listOf( + RulesConfig( + Warnings.TOO_MANY_LINES_IN_LAMBDA.name, true, + mapOf("maxLambdaLength" to "3")) + ) + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `less than max`() { + lintMethod( + """ + |fun foo() { + | val x = 10 + | val list = listOf(1, 2, 3, 4, 5) + | .map {element -> element + x} + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `nested lambda with implicit parameter`() { + lintMethod( + """ + |fun foo() { + | private val allTestsFromResources: List by lazy { + | val fileUrl: URL? = javaClass.getResource("123") + | val resource = fileUrl + | ?.let { File(it.file) } + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `lambda doesn't expect parameters`() { + lintMethod( + """ + |fun foo() { + | private val allTestsFromResources: List by lazy { + | val fileUrl: URL? = javaClass.getResource("123") + | list = listOf(1, 2, 3, 4, 5) + | .removeAt(1) + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `less than max without argument`() { + lintMethod( + """ + |fun foo() { + | val x = 10 + | val list = listOf(1, 2, 3, 4, 5) + | .map {it + x} + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `more than max with argument`() { + lintMethod( + """ + |fun foo() { + | val calculateX = { x : Int -> + | when(x) { + | in 0..40 -> "Fail" + | in 41..70 -> "Pass" + | in 71..100 -> "Distinction" + | else -> false + | } + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `more than maximum without argument`() { + lintMethod( + """ + |fun foo() { + | val list = listOf(1, 2, 3, 4, 5) + | .map { + | val x = 0 + | val y = x + 1 + | val z = y + 1 + | it + z + | + | + | + | + | } + |} + """.trimMargin(), + LintError(3, 13, ruleId, "${Warnings.TOO_MANY_LINES_IN_LAMBDA.warnText()} max length lambda without arguments is 3, but you have 6", false), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `two lambda more than maximum without argument`() { + lintMethod( + """ + |fun foo() { + | val list = listOf(1, 2, 3, 4, 5) + | .filter { n -> n % 2 == 1 } + | .map { + | val x = 0 + | val y = x + 1 + | val z = y + 1 + | it + z + | + | + | + | + | } + |} + """.trimMargin(), + LintError(4, 13, ruleId, "${Warnings.TOO_MANY_LINES_IN_LAMBDA.warnText()} max length lambda without arguments is 3, but you have 6", false), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `lambda in lambda`() { + lintMethod( + """ + |fun foo() { + | val list = listOf(listOf(1,2,3), listOf(4,5,6)) + | .map {l -> l.map { + | val x = 0 + | val y = x + 1 + | val z = y + 1 + | println(it) + | } + | } + | } + """.trimMargin(), + LintError(3, 25, ruleId, "${Warnings.TOO_MANY_LINES_IN_LAMBDA.warnText()} max length lambda without arguments is 3, but you have 6", false), + rulesConfigList = rulesConfigList + ) + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt index d0369f181c..5264de2399 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/VariablesWithUsagesSearchTest.kt @@ -332,7 +332,6 @@ class VariablesWithUsagesSearchTest { val var1 = keys.elementAt(0) val var2 = keys.elementAt(1) val var3 = keys.elementAt(2) - val var4 = keys.elementAt(3) assertEquals("var v = 0", var1) assertEquals(0, vars[var1]?.size) assertEquals("var v = 1", var2) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt index 4fbc326d14..053e56d20d 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt @@ -34,24 +34,24 @@ internal fun List.assertEquals(vararg expectedLintErrors: LintError) Assertions.assertThat(this) .allSatisfy { actual -> val expected = expectedLintErrors[this.indexOf(actual)] - SoftAssertions.assertSoftly { - it + SoftAssertions.assertSoftly { sa -> + sa .assertThat(actual.line) .`as`("Line") .isEqualTo(expected.line) - it + sa .assertThat(actual.col) .`as`("Column") .isEqualTo(expected.col) - it + sa .assertThat(actual.ruleId) .`as`("Rule id") .isEqualTo(expected.ruleId) - it + sa .assertThat(actual.detail) .`as`("Detailed message") .isEqualTo(expected.detail) - it + sa .assertThat(actual.canBeAutoCorrected) .`as`("Can be autocorrected") .isEqualTo(expected.canBeAutoCorrected) diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected.kt index a344438ff6..fe33eb4e52 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) My Company., Ltd. 2012-2021. All rights reserved. - */ + Copyright (c) My Company., Ltd. 2012-2021. All rights reserved. +*/ /** * Lorem ipsum * dolor sit amet diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest.kt index fb10f5bcf9..89caa66aa6 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearTest.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) My Company., Ltd. 2012-2019. All rights reserved. - */ + Copyright (c) My Company., Ltd. 2012-2019. All rights reserved. +*/ /** * Lorem ipsum * dolor sit amet diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightShouldNotTriggerNPEExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightShouldNotTriggerNPEExpected.kt new file mode 100644 index 0000000000..fe33eb4e52 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightShouldNotTriggerNPEExpected.kt @@ -0,0 +1,19 @@ +/* + Copyright (c) My Company., Ltd. 2012-2021. All rights reserved. +*/ +/** + * Lorem ipsum + * dolor sit amet + */ + +package test.paragraph2.header + +import org.cqfn.diktat.example.A +import org.cqfn.diktat.example.B + +/** + * Example class + */ +class Example { + lateinit var map: Map +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightShouldNotTriggerNPETest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightShouldNotTriggerNPETest.kt new file mode 100644 index 0000000000..a0e91a476b --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/header/CopyrightShouldNotTriggerNPETest.kt @@ -0,0 +1,16 @@ +/** + * Lorem ipsum + * dolor sit amet + */ + +package test.paragraph2.header + +import org.cqfn.diktat.example.A +import org.cqfn.diktat.example.B + +/** + * Example class + */ +class Example { + lateinit var map: Map +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocExpected.kt index 44ca966073..aaed183d0e 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocExpected.kt @@ -1,8 +1,6 @@ /* Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved. */ - - /** * Lorem ipsum * dolor sit amet diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocTest.kt index c9437d5cc1..2417795db7 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/MisplacedHeaderKdocTest.kt @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved. */ package test.paragraph2.header diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightExample.kt b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightExample.kt index db771bc4f3..0370a2fd65 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightExample.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightExample.kt @@ -18,4 +18,4 @@ class SomeClass { fun coolFun() { val a = 5 } -} \ No newline at end of file +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerExample.kt b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerExample.kt index 3b9b0af741..93186bf742 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerExample.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerExample.kt @@ -1,5 +1,5 @@ /* - Copyright 2018-2020 John Doe. + Copyright 2018-2021 John Doe. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ class SomeClass { fun function() { } -} \ No newline at end of file +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerTest.kt index 3b9b0af741..93186bf742 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightNotTriggerTest.kt @@ -1,5 +1,5 @@ /* - Copyright 2018-2020 John Doe. + Copyright 2018-2021 John Doe. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ class SomeClass { fun function() { } -} \ No newline at end of file +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightTest.kt index 481563563b..f4a1441370 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/MultilineCopyrightTest.kt @@ -2,4 +2,4 @@ class SomeClass { fun coolFun() { val a = 5 } -} \ No newline at end of file +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocExpected.kt index 17d54923d7..84cc556f19 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocExpected.kt @@ -1,7 +1,6 @@ /* Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved. */ - /** * This is a file used in unit test */ diff --git a/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocTest.kt b/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocTest.kt index 455c530d4c..12b0028ebf 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/header/NewlineAfterHeaderKdocTest.kt @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved. */ /** * This is a file used in unit test diff --git a/diktat-rules/src/test/resources/test/paragraph5/method_call_names/ReplaceMethodCallNamesExpected.kt b/diktat-rules/src/test/resources/test/paragraph5/method_call_names/ReplaceMethodCallNamesExpected.kt new file mode 100644 index 0000000000..00606b0492 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph5/method_call_names/ReplaceMethodCallNamesExpected.kt @@ -0,0 +1,25 @@ +package test.chapter6.method_call_names + +fun coolFunction() { + val list = listOf(1, 2, 3) + val testStr = "" + + if (list.isEmpty()) { } + + if (list.isNotEmpty()) { } + + if (list.isNotEmpty()) { } + + if (list.isEmpty()) { } + + if (testStr.isBlank()) { } + + if (testStr.isNotBlank()) { } + + if (testStr.isNotBlank()) { } + + if (testStr.isBlank()) { } + + if (list.isNotEmpty() && testStr.isBlank()) { } +} + diff --git a/diktat-rules/src/test/resources/test/paragraph5/method_call_names/ReplaceMethodCallNamesTest.kt b/diktat-rules/src/test/resources/test/paragraph5/method_call_names/ReplaceMethodCallNamesTest.kt new file mode 100644 index 0000000000..49b999d2a0 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph5/method_call_names/ReplaceMethodCallNamesTest.kt @@ -0,0 +1,25 @@ +package test.chapter6.method_call_names + +fun coolFunction() { + val list = listOf(1, 2, 3) + val testStr = "" + + if (list.isEmpty()) { } + + if (list.isNotEmpty()) { } + + if (!list.isEmpty()) { } + + if (!list.isNotEmpty()) { } + + if (testStr.isBlank()) { } + + if (testStr.isNotBlank()) { } + + if (!testStr.isBlank()) { } + + if (!testStr.isNotBlank()) { } + + if (!list.isEmpty() && !testStr.isNotBlank()) { } +} + diff --git a/examples/gradle-groovy-dsl/diktat-analysis.yml b/examples/gradle-groovy-dsl/diktat-analysis.yml index cf353c0ce0..e3fcb8b435 100644 --- a/examples/gradle-groovy-dsl/diktat-analysis.yml +++ b/examples/gradle-groovy-dsl/diktat-analysis.yml @@ -111,6 +111,9 @@ # Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after - name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS enabled: true +# Checks that kdoc does not contain @author tag or date +- name: KDOC_CONTAINS_DATE_OR_AUTHOR + enabled: true # Checks that KDoc does not contain single line with words 'return', 'get' or 'set' - name: KDOC_TRIVIAL_KDOC_ON_FUNCTION enabled: true @@ -129,9 +132,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 diff --git a/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml b/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml index 1dff285a5f..7c503cf769 100644 --- a/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml +++ b/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml @@ -71,6 +71,8 @@ enabled: true - name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS enabled: true +- name: KDOC_CONTAINS_DATE_OR_AUTHOR + enabled: true - name: KDOC_TRIVIAL_KDOC_ON_FUNCTION enabled: 'true' - name: HEADER_WRONG_FORMAT @@ -84,8 +86,6 @@ copyrightText: 'Copyright (c) Your Company Name Here. 2010-2021' - name: HEADER_NOT_BEFORE_PACKAGE enabled: true -- name: HEADER_CONTAINS_DATE_OR_AUTHOR - enabled: true - name: FILE_IS_TOO_LONG enabled: true configuration: diff --git a/examples/gradle-kotlin-dsl/diktat-analysis.yml b/examples/gradle-kotlin-dsl/diktat-analysis.yml index cf353c0ce0..e3fcb8b435 100644 --- a/examples/gradle-kotlin-dsl/diktat-analysis.yml +++ b/examples/gradle-kotlin-dsl/diktat-analysis.yml @@ -111,6 +111,9 @@ # Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after - name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS enabled: true +# Checks that kdoc does not contain @author tag or date +- name: KDOC_CONTAINS_DATE_OR_AUTHOR + enabled: true # Checks that KDoc does not contain single line with words 'return', 'get' or 'set' - name: KDOC_TRIVIAL_KDOC_ON_FUNCTION enabled: true @@ -129,9 +132,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 diff --git a/examples/maven/diktat-analysis.yml b/examples/maven/diktat-analysis.yml index cf353c0ce0..e3fcb8b435 100644 --- a/examples/maven/diktat-analysis.yml +++ b/examples/maven/diktat-analysis.yml @@ -111,6 +111,9 @@ # Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after - name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS enabled: true +# Checks that kdoc does not contain @author tag or date +- name: KDOC_CONTAINS_DATE_OR_AUTHOR + enabled: true # Checks that KDoc does not contain single line with words 'return', 'get' or 'set' - name: KDOC_TRIVIAL_KDOC_ON_FUNCTION enabled: true @@ -129,9 +132,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 diff --git a/info/available-rules.md b/info/available-rules.md index 085763b398..88375589e2 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -1,9 +1,9 @@ | Chap | Standard | Rule name | Description | Fix | Config | FixMe | | ----- | ---------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --- | ------ | ----- | | 1 | 1.1.1 | CONFUSING_IDENTIFIER_NAMING | Check: warns if identifier has inappropriate name (See table of rule 1.2 part 6). | no | no | no | -| 1 | 1.2.1 | PACKAGE_NAME_MISSING | Check: warns if package name is missing in a file
Fix: automatically adds package directive with the name that starts from the domain name (in example - com.huawei) and contains the real directory | yes | no | Domain name should not be hardcoded. It should be moved to extra configuration.Recursively fix all imports in project.Fix the directory where the code is stored.Make this check isolated from domain name addition| +| 1 | 1.2.1 | PACKAGE_NAME_MISSING | Check: warns if package name is missing in a file
Fix: automatically adds package directive with the name that starts from the domain name (in example - com.huawei) and contains the real directory | yes | no | Recursively fix all imports in project.
Fix the directory where the code is stored.
Make this check isolated from domain name addition | | 1 | 1.2.1 | PACKAGE_NAME_INCORRECT_CASE | Check: warns if package name is in incorrect (non-lower) case
Fix: automatically update the case in package name | yes | no | Recursively update all imports in the project.| -| 1 | 1.2.1 | PACKAGE_NAME_INCORRECT_PREFIX | Check: warns if package name does not start with the company's domain
Fix: automatically update the prefix in the package name | yes | no | Fix the directory where the code is stored.Recursively update all imports in the project.Domain name should not be hardcoded. It should be moved to extra configuration.| +| 1 | 1.2.1 | PACKAGE_NAME_INCORRECT_PREFIX | Check: warns if package name does not start with the company's domain
Fix: automatically update the prefix in the package name | yes | no | Fix the directory where the code is stored.
Recursively update all imports in the project.| | 1 | 1.2.1 | PACKAGE_NAME_INCORRECT_SYMBOLS | Check: warns if package name has incorrect symbols like underscore or non-ASCII letters/digits.Exception: underscores that are used for differentiating of keywords in a name.
Fix: no but will be | no | no | Add autofix for at least converting underscore to a dot or replacing itFix the directory where the code is stored.Cover autofix with tests| | 1 | 1.2.1 | PACKAGE_NAME_INCORRECT_PATH | Check: warns if the path for a file does not match with a package name
Fix: replacing incorrect package name with the name constructed from a path to the file. | yes | no | Make this check isolated from domain name creationRecursively update all imports in the project.Fix the directory where the code is stored.Add test mechanism to test checker| | 1 | 1.2.1 | INCORRECT_PACKAGE_SEPARATOR | Check: warns if underscore is incorrectly used to split package naming
Fix: fixing all nodes in AST and the package name to remove all underscores | no | no | Recursively update usages of this class in the project| @@ -38,12 +38,12 @@ | 2 | 2.2.1 | KDOC_NO_EMPTY_TAGS | Check: warns if KDoc tags have empty content | no | no | | | 2 | 2.1.3 | KDOC_NO_DEPRECATED_TAG | Check: warns if `@deprecated` is used in KDoc
Fix: adds `@Deprecated` annotation with message, removes tag | yes | no | Annotation's `replaceWith` field can be filled too| | 2 | 2.1.1 | KDOC_NO_CONSTRUCTOR_PROPERTY | Check: warns if there is no property tag inside KDoc before constructor | yes | no | -| 2 | 2.1.1 | KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT | Check: warns if there is comment before property in constructor | yes | no | +| 2 | 2.1.1 | KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT| Check: warns if there is comment before property in constructor | yes | no | +| 2 | 2.2.1 | KDOC_CONTAINS_DATE_OR_AUTHOR | Check: warns if header KDoc contains `@author` tag.
Warns if `@since` tag contains version and not date. | no | no | Detect author by other patterns (e.g. 'created by' etc.)| | 2 | 2.3.1 | KDOC_TRIVIAL_KDOC_ON_FUNCTION | Check: warns if KDoc contains single line with words 'return', 'get' or 'set' | no | no | | | 2 | 2.2.1 | HEADER_WRONG_FORMAT | Checks: warns if there is no newline after header KDoc
Fix: adds newline | yes | no | Check if header is on the very top of file. It's hard to determine when it's not.| | 2 | 2.2.1 | 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 | yes | mandatoryCopyright | Allow setting copyright patterns via configuration| | 2 | 2.2.1 | WRONG_COPYRIGHT_YEAR | Checks: copyright have a valid year
Fix: makes a year valid | yes | no | - | -| 2 | 2.2.1 | HEADER_CONTAINS_DATE_OR_AUTHOR | Check: warns if header KDoc contains `@author` tag | no | no | Detect author by other patterns (e.g. 'created by' etc.)Detect creation date (no standard tag even in javadoc)| | 2 | 2.2.1 | HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE | Check: warns if file with zero or >1 classes doesn't have header KDoc | no | no | | | 2 | 2.4.2 | COMMENTED_OUT_CODE | Check: warns if commented code is detected (when un-commented, can be parsed) | no | no | Offset is lost when joined EOL comments are split again| | 2 | 2.4.1 | COMMENT_WHITE_SPACE | Check: warns if there is no space between // and comment, if there is no space between code and comment
Fix: adds a white space | yes | maxSpaces | - | @@ -67,6 +67,7 @@ | 3 | 3.5.1 | LONG_LINE | Check: warns if length doesn't exceed the specified length | no | lineLength | handle json method in KDoc | | 3 | 3.6.2 | REDUNDANT_SEMICOLON | Check: warns if semicolons are used at the end of line.
Fix: removes semicolon. | yes | no | - | | 3 | 3.6.2 | WRONG_NEWLINES | Check: warns if line breaks do not follow code style guid.
Fix: fixes incorrect line breaks. | yes | no | - | +| 3 | 3.6.2 | COMPLEX_EXPRESSION | Check: warns if a long dot qualified expression is used in condition or as an argument | no | no | - | | 3 | 3.15.1 | STRING_CONCATENATION | Check: warns if in a single line concatenation of strings is used | yes | no | - | | 3 | 3.7.1 | TOO_MANY_BLANK_LINES | Check: warns if blank lines are used placed incorrectly.
Fix: removes redundant blank lines. | yes | no | | | 3 | 3.8.1 | WRONG_WHITESPACE | Check: warns if usage of horizontal spaces violates code style guide.
Fix: fixes incorrect whitespaces. | yes | no | - | @@ -91,9 +92,12 @@ | 5 | 5.1.1 | TOO_LONG_FUNCTION | Check: if length of function is too long | no | maxFunctionLength isIncludeHeader | | 5 | 5.1.2 | NESTED_BLOCK | Check if function has more nested blocks than expected | no | maxNestedBlockQuantit| | 5 | 5.1.3 | AVOID_NESTED_FUNCTIONS | Check: if there are any nested functions
Fix: declare function in the outer scope | yes | no | + | +| 5 | 5.1.4 | INVERSE_FUNCTION_PREFERRED | Check: if function call with "!" can be rewritten to the inverse function (!isEmpty() -> isNotEmpty())
Fix: Rewrites function call | yes | - | - | | 5 | 5.2.1 | LAMBDA_IS_NOT_LAST_PARAMETER | Checks that lambda inside function parameters isn't in the end | no | no | | 5 | 5.2.2 | TOO_MANY_PARAMETERS | Check: if function contains more parameters than allowed | no | maxParameterListSize | | 5 | 5.2.3 | WRONG_OVERLOADING_FUNCTION_ARGUMENTS | Check: function has overloading instead use default arguments | no | no | +| 5 | 5.2.4 | RUN_BLOCKING_INSIDE_ASYNC | Check: using runBlocking inside async block code | no | no | - | +| 5 | 5.2.5 | TOO_MANY_LINES_IN_LAMBDA | Check: that the long lambda has parameters | no | maxLambdaLength | | 6 | 6.1.1 | SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY | Check: warns if there is only one secondary constructor in a class
Fix: converts it to a primary constructor | yes | no | Support more complicated logic of constructor conversion | | 6 | 6.1.2 | USE_DATA_CLASS | Check: if class can be made as data class | no | no | yes | | 6 | 6.1.3 | EMPTY_PRIMARY_CONSTRUCTOR | Check: if there is empty primary constructor | yes | no | yes | diff --git a/info/buildSrc/gradle.properties b/info/buildSrc/gradle.properties index 66cd2f5a2f..f9343f1708 100644 --- a/info/buildSrc/gradle.properties +++ b/info/buildSrc/gradle.properties @@ -1 +1 @@ -version=0.2.1-SNAPSHOT \ No newline at end of file +version=0.3.1-SNAPSHOT \ No newline at end of file diff --git a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationAvailableRules.kt b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationAvailableRules.kt index 9585927619..d1757c9c53 100644 --- a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationAvailableRules.kt +++ b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationAvailableRules.kt @@ -18,7 +18,7 @@ fun generateAvailableRules(rootDir: File, wpDir: File) { .map { it[2].replace("\\s+".toRegex(), "") to it[5] } .forEach { ruleMap[it.first]!!.config = it.second} val newText = File(wpDir, "sections/appendix.tex").readLines().toMutableList() - newText.removeAll(newText.subList(newText.indexOf("\\section*{Available Rules}") + 1, newText.indexOf("\\lstMakeShortInline[basicstyle=\\ttfamily\\bfseries]`"))) + newText.removeAll(newText.subList(newText.indexOf("\\section*{Available Rules}") + 1, newText.indexOf("%CodeStyle"))) var index = newText.indexOf("\\section*{Available Rules}") + 1 AUTO_TABLE.trimIndent().lines().forEach { newText.add(index++, it) } ruleMap.map { it.value } diff --git a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationDocs.kt b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationDocs.kt index f3584d6b5e..483d8973d8 100644 --- a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationDocs.kt +++ b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/GenerationDocs.kt @@ -5,6 +5,7 @@ package org.cqfn.diktat.generation.docs import java.io.File import java.io.PrintWriter + /** * Adds/updates diktat code style in white paper document. */ @@ -22,15 +23,14 @@ fun generateCodeStyle(guideDir: File, wpDir: File) { val lines = file.readLines().toMutableList().drop(1) tempFile.printWriter().use { writer -> val iterator = lines.iterator() - writer.writeln("\\lstMakeShortInline[basicstyle=\\ttfamily\\bfseries]`") + writer.writeWithoutApostrophe("%CodeStyle") while (iterator.hasNext()) { val line = iterator.next() if (line.contains("## Preface")) - break; - + break when { line.startsWith("#") -> { - writer.writeln("\\section*{${line.removePrefix("#").trim()}}") + writer.writeWithoutApostrophe("\\section*{${line.removePrefix("#").trim()}}") } line.startsWith("*") -> { writeTableContentLine(writer, line, 0.5) @@ -49,8 +49,7 @@ fun generateCodeStyle(guideDir: File, wpDir: File) { if (line.contains(" ### Purpose of this document @@ -434,7 +443,7 @@ The basic format of KDoc is shown in the following example: * Other ... */ fun method(arg: String) { - // … + // ... } ``` @@ -509,7 +518,7 @@ When the method has such details as arguments, return value, or can throw except /** * This is the short overview comment for the example interface. * / * Add a blank line between the comment text and each KDoc tag underneath * / - * @since 2019-01-01 + * @since 1.6 */ protected abstract class Sample { /** @@ -551,8 +560,11 @@ Therefore, Kdoc should contain the following: Kdoc should not contain: - Empty descriptions in tag blocks. It is better not to write Kdoc than waste code line space. - There should be no empty lines between the method/class declaration and the end of Kdoc (`*/` symbols). -Important note: KDoc does not support the `@deprecated` tag. Instead, use the `@Deprecated` annotation. - +- `@author` tag. It doesn't matter who originally created a class when you can use `git blame` or VCS of your choice to look through the changes history. +Important notes: +- KDoc does not support the `@deprecated` tag. Instead, use the `@Deprecated` annotation. +- The `@since` tag should be used for versions only. Do not use dates in `@since` tag, it's confusing and less accurate. + If a tag block cannot be described in one line, indent the content of the new line by *four spaces* from the `@` position to achieve alignment (`@` counts as one + three spaces). **Exception:** @@ -587,20 +599,38 @@ Other KDoc tags (such as @param type parameters and @see.) can be added as follo ### 2.2 Adding comments on the file header This section describes the general rules of adding comments on the file header. -Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. -Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). Also, describe the content inside files that contain multiple or no classes. +### 2.2.1 Formatting of comments in the file header + +Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. -Place comments on the file header before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. +Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). +Also, describe the content inside files that contain multiple or no classes. The following examples for Huawei describe the format of the *copyright license*: \ Chinese version: `版权所有 (c) 华为技术有限公司 2012-2020` \ English version: `Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved.` +`2012` and `2020` are the years the file was first created and the current year, respectively. + +Do not place **release notes** in header, use VCS to keep track of changes in file. Notable changes can be marked in individual KDocs using `@since` tag with version. -Regarding the **release notes**, see examples below: +Invalid example: +```kotlin +/** + * Release notes: + * 2019-10-11: added class Foo + */ -- `2012-2020` can be modified according to your actual situation. `2012` and `2020` are the years the file was first created and last modified, respectively. -These two years can be the same (for example, `2020–2020`). When the file is substantially changed (for example, through feature extensions and major refactorings), the subsequent years must be updated. +class Foo +``` + +Valid example: +```kotlin +/** + * @since 2.4.0 + */ +class Foo +``` - The **copyright statement** can use your company's subsidiaries, as shown in the below examples: \ Chinese version: `版权所有 (c) 海思半导体 2012-2020` \ @@ -1877,6 +1907,30 @@ fun foo() { println("Nested Output: ${nested()}") } ``` +#### 5.1.4 Negated function calls +Don't use negated function calls if it can be replaced with negated version of this function + +**Invalid example**: +```kotlin +fun foo() { + val list = listOf(1, 2, 3) + + if (!list.isEmpty()) { + // Some cool logic + } +} +``` + +**Valid example**: +```kotlin +fun foo() { + val list = listOf(1, 2, 3) + + if (list.isNotEmpty()) { + // Some cool logic + } +} +``` ### 5.2 Function arguments diff --git a/info/guide/guide-TOC.md b/info/guide/guide-TOC.md index ec5ef6e66d..e8e7a8c9a9 100644 --- a/info/guide/guide-TOC.md +++ b/info/guide/guide-TOC.md @@ -1,3 +1,6 @@ + + +# Diktat Coding Style Guide, v.0.0.1 # Table of contents I [Preface](#c0) @@ -27,7 +30,7 @@ I [Preface](#c0) * [2.4 Code comments](#c2.4) * [2.4.1 Add a blank line between the body of the comment and Kdoc tag-blocks](#r2.4.1) * [2.4.2 Do not comment on unused code blocks](#r2.4.2) - * [2.4.3 Do not comment on unused code blocks](#r2.4.3) + * [2.4.3 Code delivered to the client should not contain TODO/FIXME comments](#r2.4.3) [3. General formatting (typesetting)](#c3) * [3.1 File-related rules](#c3.1) @@ -79,11 +82,13 @@ I [Preface](#c0) * [5.1 Function design](#c5.1) * [5.1.1 Avoid functions that are too long ](#r5.1.1) * [5.1.2 Avoid deep nesting of function code blocks, limiting to four levels](#r5.1.2) - * [5.1.3 Avoid using nested functions](#r5.1.3) + * [5.1.3 Avoid using nested functions](#r5.1.3) + * [5.1.4 Negated function calls](#r5.1.4) * [5.2 Function arguments](#c5.2) * [5.2.1 The lambda parameter of the function should be placed at the end of the argument list](#r5.2.1) * [5.2.2 Number of function parameters should be limited to five](#r5.2.2) * [5.2.3 Use default values for function arguments instead of overloading them](#r5.2.3) + * [5.2.5 Long lambdas should have explicit parameters](#r5.2.4) [6. Classes, interfaces, and extension functions](#c6) * [6.1 Classes](#c6.1) diff --git a/info/guide/guide-chapter-0.md b/info/guide/guide-chapter-0.md index 7d010dbacf..54774996a7 100644 --- a/info/guide/guide-chapter-0.md +++ b/info/guide/guide-chapter-0.md @@ -1,3 +1,8 @@ +# Diktat Coding Style Guide +# International version, v.0.0.1 + + + ## Preface ### Purpose of this document diff --git a/info/guide/guide-chapter-2.md b/info/guide/guide-chapter-2.md index afe3550248..9d80131781 100644 --- a/info/guide/guide-chapter-2.md +++ b/info/guide/guide-chapter-2.md @@ -19,7 +19,7 @@ The basic format of KDoc is shown in the following example: * Other ... */ fun method(arg: String) { - // … + // ... } ``` @@ -94,7 +94,7 @@ When the method has such details as arguments, return value, or can throw except /** * This is the short overview comment for the example interface. * / * Add a blank line between the comment text and each KDoc tag underneath * / - * @since 2019-01-01 + * @since 1.6 */ protected abstract class Sample { /** @@ -136,8 +136,11 @@ Therefore, Kdoc should contain the following: Kdoc should not contain: - Empty descriptions in tag blocks. It is better not to write Kdoc than waste code line space. - There should be no empty lines between the method/class declaration and the end of Kdoc (`*/` symbols). -Important note: KDoc does not support the `@deprecated` tag. Instead, use the `@Deprecated` annotation. - +- `@author` tag. It doesn't matter who originally created a class when you can use `git blame` or VCS of your choice to look through the changes history. +Important notes: +- KDoc does not support the `@deprecated` tag. Instead, use the `@Deprecated` annotation. +- The `@since` tag should be used for versions only. Do not use dates in `@since` tag, it's confusing and less accurate. + If a tag block cannot be described in one line, indent the content of the new line by *four spaces* from the `@` position to achieve alignment (`@` counts as one + three spaces). **Exception:** @@ -172,20 +175,38 @@ Other KDoc tags (such as @param type parameters and @see.) can be added as follo ### 2.2 Adding comments on the file header This section describes the general rules of adding comments on the file header. -Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. -Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). Also, describe the content inside files that contain multiple or no classes. +### 2.2.1 Formatting of comments in the file header -Place comments on the file header before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. +Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. + +Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). +Also, describe the content inside files that contain multiple or no classes. The following examples for Huawei describe the format of the *copyright license*: \ Chinese version: `版权所有 (c) 华为技术有限公司 2012-2020` \ English version: `Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved.` +`2012` and `2020` are the years the file was first created and the current year, respectively. + +Do not place **release notes** in header, use VCS to keep track of changes in file. Notable changes can be marked in individual KDocs using `@since` tag with version. + +Invalid example: +```kotlin +/** + * Release notes: + * 2019-10-11: added class Foo + */ -Regarding the **release notes**, see examples below: +class Foo +``` -- `2012-2020` can be modified according to your actual situation. `2012` and `2020` are the years the file was first created and last modified, respectively. -These two years can be the same (for example, `2020–2020`). When the file is substantially changed (for example, through feature extensions and major refactorings), the subsequent years must be updated. +Valid example: +```kotlin +/** + * @since 2.4.0 + */ +class Foo +``` - The **copyright statement** can use your company's subsidiaries, as shown in the below examples: \ Chinese version: `版权所有 (c) 海思半导体 2012-2020` \ diff --git a/info/guide/guide-chapter-3.md b/info/guide/guide-chapter-3.md index 5e47c6cdf9..ff20ad9c30 100644 --- a/info/guide/guide-chapter-3.md +++ b/info/guide/guide-chapter-3.md @@ -366,6 +366,22 @@ Note that all comparison operators, such as `==`, `>`, `<`, should not be split. ```kotlin if (condition) list.map { foo(it) }.filter { bar(it) } else list.drop(1) ``` + +**Note:** If dot qualified expression is inside condition or passed as an argument - it should be replaced with new variable. + +**Invalid example**: +```kotlin + if (node.treeParent.treeParent.findChildByType(IDENTIFIER) != null) {} +``` + +**Valid example**: +```kotlin + val grandIdentifier = node + .treeParent + .treeParent + .findChildByType(IDENTIFIER) + if (grandIdentifier != null) {} +``` 2) Newlines should be placed after the assignment operator (`=`). 3) In function or class declarations, the name of a function or constructor should not be split by a newline from the opening brace `(`. diff --git a/info/guide/guide-chapter-5.md b/info/guide/guide-chapter-5.md index d9623e54f2..ea27034443 100644 --- a/info/guide/guide-chapter-5.md +++ b/info/guide/guide-chapter-5.md @@ -59,6 +59,30 @@ fun foo() { println("Nested Output: ${nested()}") } ``` +#### 5.1.4 Negated function calls +Don't use negated function calls if it can be replaced with negated version of this function + +**Invalid example**: +```kotlin +fun foo() { + val list = listOf(1, 2, 3) + + if (!list.isEmpty()) { + // Some cool logic + } +} +``` + +**Valid example**: +```kotlin +fun foo() { + val list = listOf(1, 2, 3) + + if (list.isNotEmpty()) { + // Some cool logic + } +} +``` ### 5.2 Function arguments @@ -108,3 +132,17 @@ private fun foo() { // ... } ``` +#### 5.2.4 Synchronizing code inside asynchronous code +Try to avoid using runBlocking in asynchronous code + +**Invalid example**: +```kotlin +GlobalScope.async { + runBlocking { + count++ + } +} +``` +#### 5.2.5 Long lambdas should have explicit parameters +The lambda without parameters shouldn't be too long. +If a lambda is too long, it can confuse the user. Lambda without parameters should consist of 10 lines (non-empty and non-comment) in total. diff --git a/info/rules-mapping.md b/info/rules-mapping.md index 98d0a914eb..c6fcd96767 100644 --- a/info/rules-mapping.md +++ b/info/rules-mapping.md @@ -37,11 +37,11 @@ | KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | | KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | | KDOC_NO_DEPRECATED_TAG | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_CONTAINS_DATE_OR_AUTHOR | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | no | | KDOC_NO_EMPTY_TAGS | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | no | | HEADER_WRONG_FORMAT | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | | HEADER_MISSING_OR_WRONG_COPYRIGHT | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | | WRONG_COPYRIGHT_YEAR | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | -| HEADER_CONTAINS_DATE_OR_AUTHOR | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | no | | HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | no | | HEADER_NOT_BEFORE_PACKAGE | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | | KDOC_TRIVIAL_KDOC_ON_FUNCTION | [2.3.1](guide/diktat-coding-convention.md#r2.3.1) | no | @@ -90,6 +90,7 @@ | TOO_LONG_FUNCTION | [5.1.1](guide/diktat-coding-convention.md#r5.1.1) | no | | NESTED_BLOCK | [5.1.2](guide/diktat-coding-convention.md#r5.1.2) | no | | AVOID_NESTED_FUNCTIONS | [5.1.3](guide/diktat-coding-convention.md#r5.1.3) | yes | +| INVERSE_FUNCTION_PREFERRED | [5.1.4](guide/diktat-coding-convention.md#r5.1.4) | yes | | LAMBDA_IS_NOT_LAST_PARAMETER | [5.2.1](guide/diktat-coding-convention.md#r5.2.1) | no | | TOO_MANY_PARAMETERS | [5.2.2](guide/diktat-coding-convention.md#r5.2.2) | no | | WRONG_OVERLOADING_FUNCTION_ARGUMENTS | [5.2.3](guide/diktat-coding-convention.md#r5.2.3) | no | diff --git a/wp/README.md b/wp/README.md new file mode 100644 index 0000000000..6e7062467e --- /dev/null +++ b/wp/README.md @@ -0,0 +1,9 @@ +## White paper +To generate a pdf document, you need to go to the `wp` folder and execute +``` +make +``` + +**Note** + +Minimum `MiKTeX` version - `20.11` \ No newline at end of file diff --git a/wp/makefile b/wp/makefile new file mode 100644 index 0000000000..adcb289be1 --- /dev/null +++ b/wp/makefile @@ -0,0 +1,2 @@ +all: + pdflatex -interaction=nonstopmode wp.tex diff --git a/wp/references.bib b/wp/references.bib index 2debfb190b..c68344a919 100644 --- a/wp/references.bib +++ b/wp/references.bib @@ -2,8 +2,6 @@ %% This BibTeX bibliography file was created using BibDesk. %% http://bibdesk.sourceforge.net/ -%% Created for Daniel Lawson at 2016-03-27 19:31:34 +0100 - %% Saved with string encoding Unicode (UTF-8) @@ -61,6 +59,14 @@ @book{ref:cleancode publisher={Prentice Hall} } +@book{ref:gang, +title={Design Patterns}, +author={Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides}, +isbn={9780201485370}, +year={2000}, +publisher={Pearson Education} +} + @book{ref:sca, title={Static Code Analysis: Lint, Static Code Analysis, Data-Flow Analysis, Parasoft, Clang, List of Tools for Static Code Analysis}, author={LLC Books}, @@ -77,9 +83,17 @@ @book{ref:kotlinInAction year={2017}, publisher={ Manning Publications} } + @article{ref:offKotlin, Author = {Jetbrains}, Title = {Kotlin official documentation}, url = "https://kotlinlang.org/docs/reference/", year = {2020} -} \ No newline at end of file +} + +@article{ref:cicd, + Author = {Sten Pittet}, + Title = {Continuous integration vs. continuous delivery vs. continuous deployment}, + url = "https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment", + year = {2017} +} diff --git a/wp/sections/appendix.tex b/wp/sections/appendix.tex index 6ad32803fc..7db1fa7b5c 100644 --- a/wp/sections/appendix.tex +++ b/wp/sections/appendix.tex @@ -16,116 +16,123 @@ \section*{Available Rules} \hline \textbf{diKTat rule} & \textbf{code style} & \textbf{autofix} & \textbf{config} \\ \hline -VARIABLE\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.1.1]{ 1.1.1} & no & no \\ -VARIABLE\underline{ }HAS\underline{ }PREFIX & \hyperref[sec:1.1.1]{ 1.1.1} & yes & no \\ -IDENTIFIER\underline{ }LENGTH & \hyperref[sec:1.1.1]{ 1.1.1} & no & no \\ -GENERIC\underline{ }NAME & \hyperref[sec:1.1.1]{ 1.1.1} & yes & no \\ -BACKTICKS\underline{ }PROHIBITED & \hyperref[sec:1.1.1]{ 1.1.1} & no & no \\ -FILE\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.1.1]{ 1.1.1} & yes & no \\ -EXCEPTION\underline{ }SUFFIX & \hyperref[sec:1.1.1]{ 1.1.1} & yes & no \\ -CONFUSING\underline{ }IDENTIFIER\underline{ }NAMING & \hyperref[sec:1.1.1]{ 1.1.1} & no & no \\ -PACKAGE\underline{ }NAME\underline{ }MISSING & \hyperref[sec:1.2.1]{ 1.2.1} & yes & no \\ -PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE & \hyperref[sec:1.2.1]{ 1.2.1} & yes & no \\ -PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }PREFIX & \hyperref[sec:1.2.1]{ 1.2.1} & yes & no \\ -PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }SYMBOLS & \hyperref[sec:1.2.1]{ 1.2.1} & no & no \\ -PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }PATH & \hyperref[sec:1.2.1]{ 1.2.1} & yes & no \\ -INCORRECT\underline{ }PACKAGE\underline{ }SEPARATOR & \hyperref[sec:1.2.1]{ 1.2.1} & yes & no \\ -CLASS\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.3.1]{ 1.3.1} & yes & no \\ -OBJECT\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.3.1]{ 1.3.1} & yes & no \\ -ENUM\underline{ }VALUE & \hyperref[sec:1.3.1]{ 1.3.1} & yes & enumStyle: snakeCase, pascalCase \\ -FUNCTION\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE & \hyperref[sec:1.4.1]{ 1.4.1} & yes & no \\ -CONSTANT\underline{ }UPPERCASE & \hyperref[sec:1.5.1]{ 1.5.1} & yes & no \\ -VARIABLE\underline{ }NAME\underline{ }INCORRECT\underline{ }FORMAT & \hyperref[sec:1.6.1]{ 1.6.1} & yes & no \\ -FUNCTION\underline{ }BOOLEAN\underline{ }PREFIX & \hyperref[sec:1.6.2]{ 1.6.2} & yes & no \\ -MISSING\underline{ }KDOC\underline{ }TOP\underline{ }LEVEL & \hyperref[sec:2.1.1]{ 2.1.1} & no & no \\ -MISSING\underline{ }KDOC\underline{ }CLASS\underline{ }ELEMENTS & \hyperref[sec:2.1.1]{ 2.1.1} & no & no \\ -MISSING\underline{ }KDOC\underline{ }ON\underline{ }FUNCTION & \hyperref[sec:2.1.1]{ 2.1.1} & yes & no \\ -KDOC\underline{ }NO\underline{ }CONSTRUCTOR\underline{ }PROPERTY & \hyperref[sec:2.1.1]{ 2.1.1} & yes & no \\ -KDOC\underline{ }EXTRA\underline{ }PROPERTY & \hyperref[sec:2.1.1]{ 2.1.1} & no & no \\ -KDOC\underline{ }NO\underline{ }CONSTRUCTOR\underline{ }PROPERTY\underline{ }WITH\underline{ }COMMENT & \hyperref[sec:2.1.1]{ 2.1.1} & yes & no \\ -KDOC\underline{ }WITHOUT\underline{ }PARAM\underline{ }TAG & \hyperref[sec:2.1.2]{ 2.1.2} & yes & no \\ -KDOC\underline{ }WITHOUT\underline{ }RETURN\underline{ }TAG & \hyperref[sec:2.1.2]{ 2.1.2} & yes & no \\ -KDOC\underline{ }WITHOUT\underline{ }THROWS\underline{ }TAG & \hyperref[sec:2.1.2]{ 2.1.2} & yes & no \\ -KDOC\underline{ }EMPTY\underline{ }KDOC & \hyperref[sec:2.1.3]{ 2.1.3} & no & no \\ -KDOC\underline{ }WRONG\underline{ }SPACES\underline{ }AFTER\underline{ }TAG & \hyperref[sec:2.1.3]{ 2.1.3} & yes & no \\ -KDOC\underline{ }WRONG\underline{ }TAGS\underline{ }ORDER & \hyperref[sec:2.1.3]{ 2.1.3} & yes & no \\ -KDOC\underline{ }NEWLINES\underline{ }BEFORE\underline{ }BASIC\underline{ }TAGS & \hyperref[sec:2.1.3]{ 2.1.3} & yes & no \\ -KDOC\underline{ }NO\underline{ }NEWLINES\underline{ }BETWEEN\underline{ }BASIC\underline{ }TAGS & \hyperref[sec:2.1.3]{ 2.1.3} & yes & no \\ -KDOC\underline{ }NO\underline{ }NEWLINE\underline{ }AFTER\underline{ }SPECIAL\underline{ }TAGS & \hyperref[sec:2.1.3]{ 2.1.3} & yes & no \\ -KDOC\underline{ }NO\underline{ }DEPRECATED\underline{ }TAG & \hyperref[sec:2.1.3]{ 2.1.3} & yes & no \\ -KDOC\underline{ }NO\underline{ }EMPTY\underline{ }TAGS & \hyperref[sec:2.2.1]{ 2.2.1} & no & no \\ -HEADER\underline{ }WRONG\underline{ }FORMAT & \hyperref[sec:2.2.1]{ 2.2.1} & yes & no \\ -HEADER\underline{ }MISSING\underline{ }OR\underline{ }WRONG\underline{ }COPYRIGHT & \hyperref[sec:2.2.1]{ 2.2.1} & yes & mandatoryCopyright \\ -WRONG\underline{ }COPYRIGHT\underline{ }YEAR & \hyperref[sec:2.2.1]{ 2.2.1} & yes & no \\ -HEADER\underline{ }CONTAINS\underline{ }DATE\underline{ }OR\underline{ }AUTHOR & \hyperref[sec:2.2.1]{ 2.2.1} & no & no \\ -HEADER\underline{ }MISSING\underline{ }IN\underline{ }NON\underline{ }SINGLE\underline{ }CLASS\underline{ }FILE & \hyperref[sec:2.2.1]{ 2.2.1} & no & no \\ -HEADER\underline{ }NOT\underline{ }BEFORE\underline{ }PACKAGE & \hyperref[sec:2.2.1]{ 2.2.1} & yes & no \\ -KDOC\underline{ }TRIVIAL\underline{ }KDOC\underline{ }ON\underline{ }FUNCTION & \hyperref[sec:2.3.1]{ 2.3.1} & no & no \\ -WRONG\underline{ }NEWLINES\underline{ }AROUND\underline{ }KDOC & \hyperref[sec:2.4.1]{ 2.4.1} & yes & no \\ -FIRST\underline{ }COMMENT\underline{ }NO\underline{ }SPACES & \hyperref[sec:2.4.1]{ 2.4.1} & yes & no \\ -COMMENT\underline{ }WHITE\underline{ }SPACE & \hyperref[sec:2.4.1]{ 2.4.1} & yes & maxSpaces \\ -IF\underline{ }ELSE\underline{ }COMMENTS & \hyperref[sec:2.4.1]{ 2.4.1} & yes & no \\ -COMMENTED\underline{ }OUT\underline{ }CODE & \hyperref[sec:2.4.2]{ 2.4.2} & no & no \\ -FILE\underline{ }IS\underline{ }TOO\underline{ }LONG & \hyperref[sec:3.1.1]{ 3.1.1} & no & maxSize ignoreFolders \\ -FILE\underline{ }CONTAINS\underline{ }ONLY\underline{ }COMMENTS & \hyperref[sec:3.1.2]{ 3.1.2} & no & no \\ -FILE\underline{ }INCORRECT\underline{ }BLOCKS\underline{ }ORDER & \hyperref[sec:3.1.2]{ 3.1.2} & yes & no \\ -FILE\underline{ }NO\underline{ }BLANK\underline{ }LINE\underline{ }BETWEEN\underline{ }BLOCKS & \hyperref[sec:3.1.2]{ 3.1.2} & yes & no \\ -FILE\underline{ }UNORDERED\underline{ }IMPORTS & \hyperref[sec:3.1.2]{ 3.1.2} & yes & no \\ -FILE\underline{ }WILDCARD\underline{ }IMPORTS & \hyperref[sec:3.1.2]{ 3.1.2} & no & allowedWildcards \\ -FILE\underline{ }NAME\underline{ }MATCH\underline{ }CLASS & \hyperref[sec:3.1.2]{ 3.1.2} & yes & no \\ -WRONG\underline{ }ORDER\underline{ }IN\underline{ }CLASS\underline{ }LIKE\underline{ }STRUCTURES & \hyperref[sec:3.1.4]{ 3.1.4} & yes & no \\ -BLANK\underline{ }LINE\underline{ }BETWEEN\underline{ }PROPERTIES & \hyperref[sec:3.1.4]{ 3.1.4} & yes & no \\ -WRONG\underline{ }DECLARATIONS\underline{ }ORDER & \hyperref[sec:3.1.4]{ 3.1.4} & yes & no \\ -NO\underline{ }BRACES\underline{ }IN\underline{ }CONDITIONALS\underline{ }AND\underline{ }LOOPS & \hyperref[sec:3.2.1]{ 3.2.1} & yes & no \\ -BRACES\underline{ }BLOCK\underline{ }STRUCTURE\underline{ }ERROR & \hyperref[sec:3.2.2]{ 3.2.2} & yes & openBraceNewline closeBraceNewline \\ -WRONG\underline{ }INDENTATION & \hyperref[sec:3.3.1]{ 3.3.1} & yes & extendedIndentOfParameters alignedParameters extendedIndentAfterOperators extendedIndentBeforeDot indentationSize \\ -EMPTY\underline{ }BLOCK\underline{ }STRUCTURE\underline{ }ERROR & \hyperref[sec:3.4.1]{ 3.4.1} & yes & allowEmptyBlocks styleEmptyBlockWithNewline \\ -LONG\underline{ }LINE & \hyperref[sec:3.5.1]{ 3.5.1} & yes & lineLength \\ -MORE\underline{ }THAN\underline{ }ONE\underline{ }STATEMENT\underline{ }PER\underline{ }LINE & \hyperref[sec:3.6.1]{ 3.6.1} & yes & no \\ -REDUNDANT\underline{ }SEMICOLON & \hyperref[sec:3.6.2]{ 3.6.2} & yes & no \\ -WRONG\underline{ }NEWLINES & \hyperref[sec:3.6.2]{ 3.6.2} & yes & no \\ -TOO\underline{ }MANY\underline{ }BLANK\underline{ }LINES & \hyperref[sec:3.7.1]{ 3.7.1} & yes & no \\ -WRONG\underline{ }WHITESPACE & \hyperref[sec:3.8.1]{ 3.8.1} & yes & no \\ -TOO\underline{ }MANY\underline{ }CONSECUTIVE\underline{ }SPACES & \hyperref[sec:3.8.1]{ 3.8.1} & yes & maxSpaces saveInitialFormattingForEnums \\ -ENUMS\underline{ }SEPARATED & \hyperref[sec:3.9.1]{ 3.9.1} & yes & no \\ -LOCAL\underline{ }VARIABLE\underline{ }EARLY\underline{ }DECLARATION & \hyperref[sec:3.10.2]{ 3.10.2} & no & no \\ -WHEN\underline{ }WITHOUT\underline{ }ELSE & \hyperref[sec:3.11.1]{ 3.11.1} & yes & no \\ -ANNOTATION\underline{ }NEW\underline{ }LINE & \hyperref[sec:3.12.1]{ 3.12.1} & yes & no \\ -WRONG\underline{ }MULTIPLE\underline{ }MODIFIERS\underline{ }ORDER & \hyperref[sec:3.14.1]{ 3.14.1} & yes & no \\ -LONG\underline{ }NUMERICAL\underline{ }VALUES\underline{ }SEPARATED & \hyperref[sec:3.14.2]{ 3.14.2} & yes & maxLength \\ -STRING\underline{ }CONCATENATION & \hyperref[sec:3.15.1]{ 3.15.1} & no & no \\ -STRING\underline{ }TEMPLATE\underline{ }CURLY\underline{ }BRACES & \hyperref[sec:3.15.2]{ 3.15.2} & yes & no \\ -STRING\underline{ }TEMPLATE\underline{ }QUOTES & \hyperref[sec:3.15.2]{ 3.15.2} & yes & no \\ -FLOAT\underline{ }IN\underline{ }ACCURATE\underline{ }CALCULATIONS & \hyperref[sec:4.1.1]{ 4.1.1} & no & no \\ -SAY\underline{ }NO\underline{ }TO\underline{ }VAR & \hyperref[sec:4.1.3]{ 4.1.3} & no & no \\ -SMART\underline{ }CAST\underline{ }NEEDED & \hyperref[sec:4.2.1]{ 4.2.1} & yes & no \\ -TYPE\underline{ }ALIAS & \hyperref[sec:4.2.2]{ 4.2.2} & no & typeReferenceLength \\ -NULLABLE\underline{ }PROPERTY\underline{ }TYPE & \hyperref[sec:4.3.1]{ 4.3.1} & yes & no \\ -GENERIC\underline{ }VARIABLE\underline{ }WRONG\underline{ }DECLARATION & \hyperref[sec:4.3.2]{ 4.3.2} & yes & no \\ -AVOID\underline{ }NULL\underline{ }CHECKS & \hyperref[sec:4.3.3]{ 4.3.3} & no & no \\ -TOO\underline{ }LONG\underline{ }FUNCTION & \hyperref[sec:5.1.1]{ 5.1.1} & no & maxFunctionLength isIncludeHeader\\ -NESTED\underline{ }BLOCK & \hyperref[sec:5.1.2]{ 5.1.2} & no & maxNestedBlockQuantit\\ -AVOID\underline{ }NESTED\underline{ }FUNCTIONS & \hyperref[sec:5.1.3]{ 5.1.3} & yes & no \\ -LAMBDA\underline{ }IS\underline{ }NOT\underline{ }LAST\underline{ }PARAMETER & \hyperref[sec:5.2.1]{ 5.2.1} & no & no \\ -TOO\underline{ }MANY\underline{ }PARAMETERS & \hyperref[sec:5.2.2]{ 5.2.2} & no & maxParameterListSize \\ -WRONG\underline{ }OVERLOADING\underline{ }FUNCTION\underline{ }ARGUMENTS & \hyperref[sec:5.2.3]{ 5.2.3} & no & no \\ -SINGLE\underline{ }CONSTRUCTOR\underline{ }SHOULD\underline{ }BE\underline{ }PRIMARY & \hyperref[sec:6.1.1]{ 6.1.1} & yes & no \\ -USE\underline{ }DATA\underline{ }CLASS & \hyperref[sec:6.1.2]{ 6.1.2} & no & no \\ -EMPTY\underline{ }PRIMARY\underline{ }CONSTRUCTOR & \hyperref[sec:6.1.3]{ 6.1.3} & yes & no \\ -MULTIPLE\underline{ }INIT\underline{ }BLOCKS & \hyperref[sec:6.1.4]{ 6.1.4} & yes & no \\ -USELESS\underline{ }SUPERTYPE & \hyperref[sec:6.1.5]{ 6.1.5} & yes & no \\ -CLASS\underline{ }SHOULD\underline{ }NOT\underline{ }BE\underline{ }ABSTRACT & \hyperref[sec:6.1.6]{ 6.1.6} & yes & no \\ -NO\underline{ }CORRESPONDING\underline{ }PROPERTY & \hyperref[sec:6.1.7]{ 6.1.7} & no & no \\ -CUSTOM\underline{ }GETTERS\underline{ }SETTERS & \hyperref[sec:6.1.8]{ 6.1.8} & no & no \\ -WRONG\underline{ }NAME\underline{ }OF\underline{ }VARIABLE\underline{ }INSIDE\underline{ }ACCESSOR & \hyperref[sec:6.1.9]{ 6.1.9} & no & no \\ -TRIVIAL\underline{ }ACCESSORS\underline{ }ARE\underline{ }NOT\underline{ }RECOMMENDED & \hyperref[sec:6.1.10]{ 6.1.10} & yes & no \\ -COMPACT\underline{ }OBJECT\underline{ }INITIALIZATION & \hyperref[sec:6.1.11]{ 6.1.11} & yes & no \\ -EXTENSION\underline{ }FUNCTION\underline{ }SAME\underline{ }SIGNATURE & \hyperref[sec:6.2.2]{ 6.2.2} & no & no \\ -AVOID\underline{ }USING\underline{ }UTILITY\underline{ }CLASS & \hyperref[sec:6.4.1]{ 6.4.1} & no & no \\ -OBJECT\underline{ }IS\underline{ }PREFERRED & \hyperref[sec:6.4.2]{ 6.4.2} & yes & no \\ +VARIABLE\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.1.1]{1.1.1} & no & no \\ +VARIABLE\underline{ }HAS\underline{ }PREFIX & \hyperref[sec:1.1.1]{1.1.1} & yes & no \\ +IDENTIFIER\underline{ }LENGTH & \hyperref[sec:1.1.1]{1.1.1} & no & no \\ +GENERIC\underline{ }NAME & \hyperref[sec:1.1.1]{1.1.1} & yes & no \\ +BACKTICKS\underline{ }PROHIBITED & \hyperref[sec:1.1.1]{1.1.1} & no & no \\ +FILE\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.1.1]{1.1.1} & yes & no \\ +EXCEPTION\underline{ }SUFFIX & \hyperref[sec:1.1.1]{1.1.1} & yes & no \\ +CONFUSING\underline{ }IDENTIFIER\underline{ }NAMING & \hyperref[sec:1.1.1]{1.1.1} & no & no \\ +PACKAGE\underline{ }NAME\underline{ }MISSING & \hyperref[sec:1.2.1]{1.2.1} & yes & no \\ +PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE & \hyperref[sec:1.2.1]{1.2.1} & yes & no \\ +PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }PREFIX & \hyperref[sec:1.2.1]{1.2.1} & yes & no \\ +PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }SYMBOLS & \hyperref[sec:1.2.1]{1.2.1} & no & no \\ +PACKAGE\underline{ }NAME\underline{ }INCORRECT\underline{ }PATH & \hyperref[sec:1.2.1]{1.2.1} & yes & no \\ +INCORRECT\underline{ }PACKAGE\underline{ }SEPARATOR & \hyperref[sec:1.2.1]{1.2.1} & yes & no \\ +CLASS\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.3.1]{1.3.1} & yes & no \\ +OBJECT\underline{ }NAME\underline{ }INCORRECT & \hyperref[sec:1.3.1]{1.3.1} & yes & no \\ +ENUM\underline{ }VALUE & \hyperref[sec:1.3.1]{1.3.1} & yes & enumStyle: snakeCase, pascalCase \\ +FUNCTION\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE & \hyperref[sec:1.4.1]{1.4.1} & yes & no \\ +CONSTANT\underline{ }UPPERCASE & \hyperref[sec:1.5.1]{1.5.1} & yes & no \\ +VARIABLE\underline{ }NAME\underline{ }INCORRECT\underline{ }FORMAT & \hyperref[sec:1.6.1]{1.6.1} & yes & no \\ +FUNCTION\underline{ }BOOLEAN\underline{ }PREFIX & \hyperref[sec:1.6.2]{1.6.2} & yes & no \\ +MISSING\underline{ }KDOC\underline{ }TOP\underline{ }LEVEL & \hyperref[sec:2.1.1]{2.1.1} & no & no \\ +MISSING\underline{ }KDOC\underline{ }CLASS\underline{ }ELEMENTS & \hyperref[sec:2.1.1]{2.1.1} & no & no \\ +MISSING\underline{ }KDOC\underline{ }ON\underline{ }FUNCTION & \hyperref[sec:2.1.1]{2.1.1} & yes & no \\ +KDOC\underline{ }NO\underline{ }CONSTRUCTOR\underline{ }PROPERTY & \hyperref[sec:2.1.1]{2.1.1} & yes & no \\ +KDOC\underline{ }EXTRA\underline{ }PROPERTY & \hyperref[sec:2.1.1]{2.1.1} & no & no \\ +KDOC\underline{ }NO\underline{ }CONSTRUCTOR\underline{ }PROPERTY\underline{ }WITH\underline{ }COMMENT & \hyperref[sec:2.1.1]{2.1.1} & yes & no \\ +KDOC\underline{ }WITHOUT\underline{ }PARAM\underline{ }TAG & \hyperref[sec:2.1.2]{2.1.2} & yes & no \\ +KDOC\underline{ }WITHOUT\underline{ }RETURN\underline{ }TAG & \hyperref[sec:2.1.2]{2.1.2} & yes & no \\ +KDOC\underline{ }WITHOUT\underline{ }THROWS\underline{ }TAG & \hyperref[sec:2.1.2]{2.1.2} & yes & no \\ +KDOC\underline{ }EMPTY\underline{ }KDOC & \hyperref[sec:2.1.3]{2.1.3} & no & no \\ +KDOC\underline{ }WRONG\underline{ }SPACES\underline{ }AFTER\underline{ }TAG & \hyperref[sec:2.1.3]{2.1.3} & yes & no \\ +KDOC\underline{ }WRONG\underline{ }TAGS\underline{ }ORDER & \hyperref[sec:2.1.3]{2.1.3} & yes & no \\ +KDOC\underline{ }NEWLINES\underline{ }BEFORE\underline{ }BASIC\underline{ }TAGS & \hyperref[sec:2.1.3]{2.1.3} & yes & no \\ +KDOC\underline{ }NO\underline{ }NEWLINES\underline{ }BETWEEN\underline{ }BASIC\underline{ }TAGS & \hyperref[sec:2.1.3]{2.1.3} & yes & no \\ +KDOC\underline{ }NO\underline{ }NEWLINE\underline{ }AFTER\underline{ }SPECIAL\underline{ }TAGS & \hyperref[sec:2.1.3]{2.1.3} & yes & no \\ +KDOC\underline{ }NO\underline{ }DEPRECATED\underline{ }TAG & \hyperref[sec:2.1.3]{2.1.3} & yes & no \\ +KDOC\underline{ }CONTAINS\underline{ }DATE\underline{ }OR\underline{ }AUTHOR & \hyperref[sec:2.1.3]{2.1.3} & no & no \\ +KDOC\underline{ }NO\underline{ }EMPTY\underline{ }TAGS & \hyperref[sec:2.2.1]{2.2.1} & no & no \\ +HEADER\underline{ }WRONG\underline{ }FORMAT & \hyperref[sec:2.2.1]{2.2.1} & yes & no \\ +HEADER\underline{ }MISSING\underline{ }OR\underline{ }WRONG\underline{ }COPYRIGHT & \hyperref[sec:2.2.1]{2.2.1} & yes & mandatoryCopyright \\ +WRONG\underline{ }COPYRIGHT\underline{ }YEAR & \hyperref[sec:2.2.1]{2.2.1} & yes & no \\ +HEADER\underline{ }MISSING\underline{ }IN\underline{ }NON\underline{ }SINGLE\underline{ }CLASS\underline{ }FILE & \hyperref[sec:2.2.1]{2.2.1} & no & no \\ +HEADER\underline{ }NOT\underline{ }BEFORE\underline{ }PACKAGE & \hyperref[sec:2.2.1]{2.2.1} & yes & no \\ +KDOC\underline{ }TRIVIAL\underline{ }KDOC\underline{ }ON\underline{ }FUNCTION & \hyperref[sec:2.3.1]{2.3.1} & no & no \\ +WRONG\underline{ }NEWLINES\underline{ }AROUND\underline{ }KDOC & \hyperref[sec:2.4.1]{2.4.1} & yes & no \\ +FIRST\underline{ }COMMENT\underline{ }NO\underline{ }SPACES & \hyperref[sec:2.4.1]{2.4.1} & yes & no \\ +COMMENT\underline{ }WHITE\underline{ }SPACE & \hyperref[sec:2.4.1]{2.4.1} & yes & maxSpaces \\ +IF\underline{ }ELSE\underline{ }COMMENTS & \hyperref[sec:2.4.1]{2.4.1} & yes & no \\ +COMMENTED\underline{ }OUT\underline{ }CODE & \hyperref[sec:2.4.2]{2.4.2} & no & no \\ +FILE\underline{ }IS\underline{ }TOO\underline{ }LONG & \hyperref[sec:3.1.1]{3.1.1} & no & maxSize ignoreFolders \\ +FILE\underline{ }CONTAINS\underline{ }ONLY\underline{ }COMMENTS & \hyperref[sec:3.1.2]{3.1.2} & no & no \\ +FILE\underline{ }INCORRECT\underline{ }BLOCKS\underline{ }ORDER & \hyperref[sec:3.1.2]{3.1.2} & yes & no \\ +FILE\underline{ }NO\underline{ }BLANK\underline{ }LINE\underline{ }BETWEEN\underline{ }BLOCKS & \hyperref[sec:3.1.2]{3.1.2} & yes & no \\ +FILE\underline{ }UNORDERED\underline{ }IMPORTS & \hyperref[sec:3.1.2]{3.1.2} & yes & no \\ +FILE\underline{ }WILDCARD\underline{ }IMPORTS & \hyperref[sec:3.1.2]{3.1.2} & no & allowedWildcards \\ +FILE\underline{ }NAME\underline{ }MATCH\underline{ }CLASS & \hyperref[sec:3.1.2]{3.1.2} & yes & no \\ +WRONG\underline{ }ORDER\underline{ }IN\underline{ }CLASS\underline{ }LIKE\underline{ }STRUCTURES & \hyperref[sec:3.1.4]{3.1.4} & yes & no \\ +BLANK\underline{ }LINE\underline{ }BETWEEN\underline{ }PROPERTIES & \hyperref[sec:3.1.4]{3.1.4} & yes & no \\ +WRONG\underline{ }DECLARATIONS\underline{ }ORDER & \hyperref[sec:3.1.4]{3.1.4} & yes & no \\ +NO\underline{ }BRACES\underline{ }IN\underline{ }CONDITIONALS\underline{ }AND\underline{ }LOOPS & \hyperref[sec:3.2.1]{3.2.1} & yes & no \\ +BRACES\underline{ }BLOCK\underline{ }STRUCTURE\underline{ }ERROR & \hyperref[sec:3.2.2]{3.2.2} & yes & openBraceNewline closeBraceNewline \\ +WRONG\underline{ }INDENTATION & \hyperref[sec:3.3.1]{3.3.1} & yes & extendedIndentOfParameters alignedParameters extendedIndentAfterOperators extendedIndentBeforeDot indentationSize \\ +EMPTY\underline{ }BLOCK\underline{ }STRUCTURE\underline{ }ERROR & \hyperref[sec:3.4.1]{3.4.1} & yes & allowEmptyBlocks styleEmptyBlockWithNewline \\ +LONG\underline{ }LINE & \hyperref[sec:3.5.1]{3.5.1} & yes & lineLength \\ +MORE\underline{ }THAN\underline{ }ONE\underline{ }STATEMENT\underline{ }PER\underline{ }LINE & \hyperref[sec:3.6.1]{3.6.1} & yes & no \\ +REDUNDANT\underline{ }SEMICOLON & \hyperref[sec:3.6.2]{3.6.2} & yes & no \\ +WRONG\underline{ }NEWLINES & \hyperref[sec:3.6.2]{3.6.2} & yes & no \\ +TOO\underline{ }MANY\underline{ }BLANK\underline{ }LINES & \hyperref[sec:3.7.1]{3.7.1} & yes & no \\ +WRONG\underline{ }WHITESPACE & \hyperref[sec:3.8.1]{3.8.1} & yes & no \\ +TOO\underline{ }MANY\underline{ }CONSECUTIVE\underline{ }SPACES & \hyperref[sec:3.8.1]{3.8.1} & yes & maxSpaces saveInitialFormattingForEnums \\ +ENUMS\underline{ }SEPARATED & \hyperref[sec:3.9.1]{3.9.1} & yes & no \\ +LOCAL\underline{ }VARIABLE\underline{ }EARLY\underline{ }DECLARATION & \hyperref[sec:3.10.2]{3.10.2} & no & no \\ +WHEN\underline{ }WITHOUT\underline{ }ELSE & \hyperref[sec:3.11.1]{3.11.1} & yes & no \\ +ANNOTATION\underline{ }NEW\underline{ }LINE & \hyperref[sec:3.12.1]{3.12.1} & yes & no \\ +WRONG\underline{ }MULTIPLE\underline{ }MODIFIERS\underline{ }ORDER & \hyperref[sec:3.14.1]{3.14.1} & yes & no \\ +LONG\underline{ }NUMERICAL\underline{ }VALUES\underline{ }SEPARATED & \hyperref[sec:3.14.2]{3.14.2} & yes & maxNumberLength maxBlockLength \\ +STRING\underline{ }CONCATENATION & \hyperref[sec:3.15.1]{3.15.1} & no & no \\ +STRING\underline{ }TEMPLATE\underline{ }CURLY\underline{ }BRACES & \hyperref[sec:3.15.2]{3.15.2} & yes & no \\ +STRING\underline{ }TEMPLATE\underline{ }QUOTES & \hyperref[sec:3.15.2]{3.15.2} & yes & no \\ +FLOAT\underline{ }IN\underline{ }ACCURATE\underline{ }CALCULATIONS & \hyperref[sec:4.1.1]{4.1.1} & no & no \\ +SAY\underline{ }NO\underline{ }TO\underline{ }VAR & \hyperref[sec:4.1.3]{4.1.3} & no & no \\ +SMART\underline{ }CAST\underline{ }NEEDED & \hyperref[sec:4.2.1]{4.2.1} & yes & no \\ +TYPE\underline{ }ALIAS & \hyperref[sec:4.2.2]{4.2.2} & no & typeReferenceLength \\ +NULLABLE\underline{ }PROPERTY\underline{ }TYPE & \hyperref[sec:4.3.1]{4.3.1} & yes & no \\ +GENERIC\underline{ }VARIABLE\underline{ }WRONG\underline{ }DECLARATION & \hyperref[sec:4.3.2]{4.3.2} & yes & no \\ +AVOID\underline{ }NULL\underline{ }CHECKS & \hyperref[sec:4.3.3]{4.3.3} & no & no \\ +TOO\underline{ }LONG\underline{ }FUNCTION & \hyperref[sec:5.1.1]{5.1.1} & no & maxFunctionLength isIncludeHeader \\ +NESTED\underline{ }BLOCK & \hyperref[sec:5.1.2]{5.1.2} & no & maxNestedBlockQuantit\\ +AVOID\underline{ }NESTED\underline{ }FUNCTIONS & \hyperref[sec:5.1.3]{5.1.3} & yes & no \\ +INVERSE\underline{ }FUNCTION\underline{ }PREFERRED & \hyperref[sec:5.1.4]{5.1.4} & yes & - \\ +LAMBDA\underline{ }IS\underline{ }NOT\underline{ }LAST\underline{ }PARAMETER & \hyperref[sec:5.2.1]{5.2.1} & no & no \\ +TOO\underline{ }MANY\underline{ }PARAMETERS & \hyperref[sec:5.2.2]{5.2.2} & no & maxParameterListSize \\ +WRONG\underline{ }OVERLOADING\underline{ }FUNCTION\underline{ }ARGUMENTS & \hyperref[sec:5.2.3]{5.2.3} & no & no \\ +SINGLE\underline{ }CONSTRUCTOR\underline{ }SHOULD\underline{ }BE\underline{ }PRIMARY & \hyperref[sec:6.1.1]{6.1.1} & yes & no \\ +USE\underline{ }DATA\underline{ }CLASS & \hyperref[sec:6.1.2]{6.1.2} & no & no \\ +EMPTY\underline{ }PRIMARY\underline{ }CONSTRUCTOR & \hyperref[sec:6.1.3]{6.1.3} & yes & no \\ +MULTIPLE\underline{ }INIT\underline{ }BLOCKS & \hyperref[sec:6.1.4]{6.1.4} & yes & no \\ +USELESS\underline{ }SUPERTYPE & \hyperref[sec:6.1.5]{6.1.5} & yes & no \\ +CLASS\underline{ }SHOULD\underline{ }NOT\underline{ }BE\underline{ }ABSTRACT & \hyperref[sec:6.1.6]{6.1.6} & yes & no \\ +NO\underline{ }CORRESPONDING\underline{ }PROPERTY & \hyperref[sec:6.1.7]{6.1.7} & no & no \\ +CUSTOM\underline{ }GETTERS\underline{ }SETTERS & \hyperref[sec:6.1.8]{6.1.8} & no & no \\ +WRONG\underline{ }NAME\underline{ }OF\underline{ }VARIABLE\underline{ }INSIDE\underline{ }ACCESSOR & \hyperref[sec:6.1.9]{6.1.9} & no & no \\ +TRIVIAL\underline{ }ACCESSORS\underline{ }ARE\underline{ }NOT\underline{ }RECOMMENDED & \hyperref[sec:6.1.10]{6.1.10} & yes & no \\ +COMPACT\underline{ }OBJECT\underline{ }INITIALIZATION & \hyperref[sec:6.1.11]{6.1.11} & yes & no \\ +EXTENSION\underline{ }FUNCTION\underline{ }SAME\underline{ }SIGNATURE & \hyperref[sec:6.2.2]{6.2.2} & no & no \\ +AVOID\underline{ }USING\underline{ }UTILITY\underline{ }CLASS & \hyperref[sec:6.4.1]{6.4.1} & no & no \\ +OBJECT\underline{ }IS\underline{ }PREFERRED & \hyperref[sec:6.4.2]{6.4.2} & yes & no \\ \hline \end{longtable} -\lstMakeShortInline[basicstyle=\ttfamily\bfseries]` +%CodeStyle + +\hspace{0.0cm}\hyperref[sec:]{} + +\section*{Diktat Coding Style Guide, v.0.0.1} + +\hspace{0.0cm}\hyperref[sec:]{} \section*{Table of contents} @@ -183,7 +190,7 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:2.4.2]{ 2.4.2 Do not comment on unused code blocks} -\hspace{1.0cm}\hyperref[sec:2.4.3]{ 2.4.3 Do not comment on unused code blocks} +\hspace{1.0cm}\hyperref[sec:2.4.3]{ 2.4.3 Code delivered to the client should not contain TODO/FIXME comments} \hspace{1.0cm}\hyperref[sec:]{} @@ -261,7 +268,7 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:4.1.2]{ 4.1.2 Comparing numeric float type values} -\hspace{1.0cm}\hyperref[sec:4.1.3]{ 4.1.3 Try to use 'val' instead of 'var' for variable declaration SAY\_NO\_TO\_VAR} +\hspace{1.0cm}\hyperref[sec:4.1.3]{ 4.1.3 Try to use 'val' instead of 'var' for variable declaration SAY_NO_TO_VAR} \hspace{0.5cm}\hyperref[sec:4.2]{ 4.2 Types} @@ -289,6 +296,8 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:5.1.3]{ 5.1.3 Avoid using nested functions} +\hspace{1.0cm}\hyperref[sec:5.1.4]{ 5.1.4 Negated function calls} + \hspace{0.5cm}\hyperref[sec:5.2]{ 5.2 Function arguments} \hspace{1.0cm}\hyperref[sec:5.2.1]{ 5.2.1 The lambda parameter of the function should be placed at the end of the argument list} @@ -319,7 +328,7 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:6.1.8]{ 6.1.8 Avoid using custom getters and setters} -\hspace{1.0cm}\hyperref[sec:6.1.9]{ 6.1.9 Never use the name of a variable in the custom getter or setter (possible\_bug)} +\hspace{1.0cm}\hyperref[sec:6.1.9]{ 6.1.9 Never use the name of a variable in the custom getter or setter (possible_bug)} \hspace{1.0cm}\hyperref[sec:6.1.10]{ 6.1.10 No trivial getters and setters are allowed in the code} @@ -329,7 +338,7 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:6.2.1]{ 6.2.1 Use extension functions for making logic of classes less coupled} -\hspace{1.0cm}\hyperref[sec:6.2.2]{ 6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible\_bug)} +\hspace{1.0cm}\hyperref[sec:6.2.2]{ 6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)} \hspace{0.5cm}\hyperref[sec:6.3]{ 6.3 Interfaces} @@ -343,6 +352,16 @@ \section*{Table of contents} \hspace{0.0cm}\hyperref[sec:]{} +\section*{Diktat Coding Style Guide} + +\section*{International version, v.0.0.1} + +\hspace{0.0cm}\hyperref[sec:]{} + +\hspace{0.0cm}\hyperref[sec:]{} + +\hspace{0.0cm}\hyperref[sec:]{} + \subsection*{\textbf{Purpose of this document}} \label{sec:} @@ -496,11 +515,11 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} For identifiers, use the following naming conventions: -1. All identifiers should use only ASCII letters or digits, and the names should match regular expressions `\w{2,64}`. +1. All identifiers should use only ASCII letters or digits, and the names should match regular expressions \textbf{\textbackslash w\{2,64\}}. -Explanation: Each valid identifier name should match the regular expression `\w{2,64}`. +Explanation: Each valid identifier name should match the regular expression \textbf{\textbackslash w\{2,64\}}. -`{2,64}` means that the name length is 2 to 64 characters, and the length of the variable name should be proportional to its life range, functionality, and responsibility. +\textbf{\{2,64\}} means that the name length is 2 to 64 characters, and the length of the variable name should be proportional to its life range, functionality, and responsibility. Name lengths of less than 31 characters are generally recommended. However, this depends on the project. Otherwise, a class declaration with generics or inheritance from a superclass can cause line breaking. @@ -508,7 +527,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} -2. Choose file names that would describe the content. Use camel case (PascalCase) and `.kt` extension. +2. Choose file names that would describe the content. Use camel case (PascalCase) and \textbf{.kt} extension. @@ -518,7 +537,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \begin{center} -\begin{tabular}{ |p{5,0cm}|p{5,0cm}|p{5,0cm}| } +\begin{tabular}{ |p{5.0cm}|p{5.0cm}|p{5.0cm}| } \hline @@ -542,7 +561,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \end{center} -4. The usage of (``) and free naming for functions and identifiers are prohibited. For example, the following code is not recommended: +4. The usage of (\textbf{}) and free naming for functions and identifiers are prohibited. For example, the following code is not recommended: @@ -551,11 +570,11 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \end{lstlisting} -The only exception is function names in `Unit tests.` +The only exception is function names in \textbf{Unit tests.} -5. Backticks (``) should not be used for identifiers, except the names of test methods (marked with @Test annotation): +5. Backticks (\textbf{}) should not be used for identifiers, except the names of test methods (marked with @Test annotation): \begin{lstlisting}[language=Kotlin] @Test fun `my test`() { /*...*/ } @@ -564,7 +583,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \begin{center} -\begin{tabular}{ |p{5,0cm}|p{5,0cm}|p{5,0cm}| } +\begin{tabular}{ |p{5.0cm}|p{5.0cm}|p{5.0cm}| } \hline @@ -598,7 +617,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} - The i,j,k variables used in loops are part of the industry standard. One symbol can be used for such variables. -- The `e` variable can be used to catch exceptions in catch block: `catch (e: Exception) {}` +- The \textbf{e} variable can be used to catch exceptions in catch block: \textbf{catch (e: Exception) \{\}} - The Java community generally does not recommend the use of prefixes. However, when developing Android code, you can use the s and m prefixes for static and non-public non-static fields, respectively. @@ -608,7 +627,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \begin{center} -\begin{tabular}{ |p{7,5cm}|p{7,5cm}| } +\begin{tabular}{ |p{7.5cm}|p{7.5cm}| } \hline @@ -643,11 +662,11 @@ \subsubsection*{\textbf{Rule 1.2.1 Package names dots}} \label{sec:} -Package names are in lower case and separated by dots. Code developed within your company should start with `your.company.domain.` Numbers are permitted in package names. +Package names are in lower case and separated by dots. Code developed within your company should start with \textbf{your.company.domain.} Numbers are permitted in package names. -Each file should have a `package` directive. +Each file should have a \textbf{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`. +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: \textbf{org.apache.commons.lang3}, \textbf{xxx.yyy.v2}. @@ -655,11 +674,11 @@ \subsubsection*{\textbf{Rule 1.2.1 Package names dots}} -- In certain cases, such as open-source projects or commercial cooperation, package names should not start with `your.company.domain.` +- In certain cases, such as open-source projects or commercial cooperation, package names should not start with \textbf{your.company.domain.} -- If the package name starts with a number or other character that cannot be used at the beginning of the Java/Kotlin package name, then underscores are allowed. For example: `com.example._123name`. +- If the package name starts with a number or other character that cannot be used at the beginning of the Java/Kotlin package name, then underscores are allowed. For example: \textbf{com.example.\_123name}. -- Underscores are sometimes permitted if the package name contains reserved Java/Kotlin keywords, such as `org.example.hyphenated_name`, `int_.example`. +- Underscores are sometimes permitted if the package name contains reserved Java/Kotlin keywords, such as \textbf{org.example.hyphenated\_name}, \textbf{int\_.example}. @@ -681,17 +700,17 @@ \subsubsection*{\textbf{1.3.1 Classes}} \label{sec:1.3.1} -Classes, enumerations, and interface names use `UpperCamelCase` nomenclature. Follow the naming rules described below: +Classes, enumerations, and interface names use \textbf{UpperCamelCase} nomenclature. Follow the naming rules described below: -1. A class name is usually a noun (or a noun phrase) denoted using the camel case nomenclature, such as UpperCamelCase. For example: `Character` or `ImmutableList`. +1. A class name is usually a noun (or a noun phrase) denoted using the camel case nomenclature, such as UpperCamelCase. For example: \textbf{Character} or \textbf{ImmutableList}. -An interface name can also be a noun or noun phrase (such as `List`) or an adjective or adjective phrase (such as `Readable`). +An interface name can also be a noun or noun phrase (such as \textbf{List}) or an adjective or adjective phrase (such as \textbf{Readable}). -Note that verbs are not used to name classes. However, nouns (such as `Customer`, `WikiPage`, and `Account`) can be used. Try to avoid using vague words such as `Manager` and `Process`. +Note that verbs are not used to name classes. However, nouns (such as \textbf{Customer}, \textbf{WikiPage}, and \textbf{Account}) can be used. Try to avoid using vague words such as \textbf{Manager} and \textbf{Process}. -2. Test classes start with the name of the class they are testing and end with 'Test'. For example, `HashTest` or `HashIntegrationTest`. +2. Test classes start with the name of the class they are testing and end with 'Test'. For example, \textbf{HashTest} or \textbf{HashIntegrationTest}. @@ -726,17 +745,17 @@ \subsubsection*{\textbf{1.4.1 Function names should be in camel case}} \label{sec:1.4.1} -Function names should use `lowerCamelCase` nomenclature. Follow the naming rules described below: +Function names should use \textbf{lowerCamelCase} nomenclature. Follow the naming rules described below: -1. Function names are usually verbs or verb phrases denoted with the camel case nomenclature (`lowerCamelCase`). +1. Function names are usually verbs or verb phrases denoted with the camel case nomenclature (\textbf{lowerCamelCase}). -For example: `sendMessage`, `stopProcess`, or `calculateValue`. +For example: \textbf{sendMessage}, \textbf{stopProcess}, or \textbf{calculateValue}. To name functions, use the following formatting rules: -a) To get, modify, or calculate a certain value: get + non-boolean field(). Note that the Kotlin compiler automatically generates getters for some classes, applying the special syntax preferred for the 'get' fields: kotlin private val field: String get() { }. kotlin private val field: String get() { }. +a) To get, modify, or calculate a certain value: get + non-boolean field(). Note that the Kotlin compiler automatically generates getters for some classes, applying the special syntax preferred for the 'get' fields: kotlin private val field: String get() \{ \}. kotlin private val field: String get() \{ \}. \begin{lstlisting}[language=Kotlin] private val field: String @@ -747,21 +766,21 @@ \subsubsection*{\textbf{1.4.1 Function names should be in camel case}} -b) `is` + boolean variable name() +b) \textbf{is} + boolean variable name() -c) `set` + field/attribute name(). However, note that the syntax and code generation for Kotlin are completely the same as those described for the getters in point a. +c) \textbf{set} + field/attribute name(). However, note that the syntax and code generation for Kotlin are completely the same as those described for the getters in point a. -d) `has` + Noun / adjective () +d) \textbf{has} + Noun / adjective () e) verb() -Note: Note: Verb are primarily used for the action objects, such as `document.print ()` +Note: Note: Verb are primarily used for the action objects, such as \textbf{document.print ()} @@ -769,7 +788,7 @@ \subsubsection*{\textbf{1.4.1 Function names should be in camel case}} -g) The Callback function allows the names that use the preposition + verb format, such as: `onCreate()`, `onDestroy()`, `toString()`. +g) The Callback function allows the names that use the preposition + verb format, such as: \textbf{onCreate()}, \textbf{onDestroy()}, \textbf{toString()}. @@ -799,7 +818,7 @@ \subsubsection*{\textbf{1.4.1 Function names should be in camel case}} \end{lstlisting} -2. An underscore (`_`) can be included in the JUnit test function name and should be used as a separator. Each logical part is denoted in `lowerCamelCase`, for example, a typical pattern of using underscore: `pop_emptyStack`. +2. An underscore (\textbf{\_}) can be included in the JUnit test function name and should be used as a separator. Each logical part is denoted in \textbf{lowerCamelCase}, for example, a typical pattern of using underscore: \textbf{pop\_emptyStack}. \subsection*{\textbf{1.5 Constants}} @@ -814,21 +833,21 @@ \subsubsection*{\textbf{1.5.1 Using UPPER case and underscore characters in a co Constant names should be in UPPER case, words separated by underscore. The general constant naming conventions are listed below: -1. Constants are attributes created with the `const` keyword or top-level/`val` local variables of an object that holds immutable data. In most cases, constants can be identified as a `const val` property from the `object`/`companion object`/file top level. These variables contain fixed constant values that typically should never be changed by programmers. This includes basic types, strings, immutable types, and immutable collections of immutable types. The value is not constant for the object, which state can be changed. +1. Constants are attributes created with the \textbf{const} keyword or top-level/\textbf{val} local variables of an object that holds immutable data. In most cases, constants can be identified as a \textbf{const val} property from the \textbf{object}/\textbf{companion object}/file top level. These variables contain fixed constant values that typically should never be changed by programmers. This includes basic types, strings, immutable types, and immutable collections of immutable types. The value is not constant for the object, which state can be changed. -2. Constant names should contain only uppercase letters separated by an underscores. They should have a val or const val modifier to make them final explicitly. In most cases, if you need to specify a constant value, then you need to create it with the "const val" modifier. Note that not all `val` variables are constants. +2. Constant names should contain only uppercase letters separated by an underscores. They should have a val or const val modifier to make them final explicitly. In most cases, if you need to specify a constant value, then you need to create it with the "const val" modifier. Note that not all \textbf{val} variables are constants. -3. Objects with immutable content, such as `Logger` and `Lock`, can be in uppercase as constants or have camel case as regular variables. +3. Objects with immutable content, such as \textbf{Logger} and \textbf{Lock}, can be in uppercase as constants or have camel case as regular variables. -4. Use meaningful constants instead of `magic numbers`. SQL or logging strings should not be treated as magic numbers, nor should they be defined as string constants. +4. Use meaningful constants instead of \textbf{magic numbers}. SQL or logging strings should not be treated as magic numbers, nor should they be defined as string constants. -Magic constants, such as `NUM_FIVE = 5` or `NUM_5 = 5` should not be treated as constants. This is because mistakes will easily be made if they are changed to `NUM_5 = 50` or 55. +Magic constants, such as \textbf{NUM\_FIVE = 5} or \textbf{NUM\_5 = 5} should not be treated as constants. This is because mistakes will easily be made if they are changed to \textbf{NUM\_5 = 50} or 55. These constants typically represent business logic values, such as measures, capacity, scope, location, tax rate, promotional discounts, and power base multiples in algorithms. You can avoid using magic numbers with the following method: -- Using library functions and APIs. For example, instead of checking that `size == 0`, use `isEmpty()` function. To work with `time`, use built-ins from `java.time API`. +- Using library functions and APIs. For example, instead of checking that \textbf{size == 0}, use \textbf{isEmpty()} function. To work with \textbf{time}, use built-ins from \textbf{java.time API}. - Enumerations can be used to name patterns. Refer to [Recommended usage scenario for enumeration in 3.9]. @@ -869,11 +888,11 @@ \subsubsection*{\textbf{1.6.1 Non-constant field name}} A local variable cannot be treated as constant even if it is final and immutable. Therefore, it should not use the preceding rules. Names of collection type variables (sets, lists, etc.) should contain plural nouns. -For example: `var namesList: List` +For example: \textbf{var namesList: List} -Names of non-constant variables should use `lowerCamelCase`. The name of the final immutable field used to store the singleton object can use the same camel case notation. +Names of non-constant variables should use \textbf{lowerCamelCase}. The name of the final immutable field used to store the singleton object can use the same camel case notation. @@ -974,7 +993,7 @@ \subsection*{\textbf{2.1 General form of Kdoc}} * Other ... */ fun method(arg: String) { - // … + // ... } \end{lstlisting} @@ -1001,9 +1020,9 @@ \subsubsection*{\textbf{2.1.1 Using KDoc for the public}} Other code blocks can also have KDocs if needed. -Instead of using comments or KDocs before properties in the primary constructor of a class - use `@property` tag in a KDoc of a class. +Instead of using comments or KDocs before properties in the primary constructor of a class - use \textbf{@property} tag in a KDoc of a class. -All properties of the primary constructor should also be documented in a KDoc with a `@property` tag. +All properties of the primary constructor should also be documented in a KDoc with a \textbf{@property} tag. @@ -1045,7 +1064,7 @@ \subsubsection*{\textbf{2.1.1 Using KDoc for the public}} -* For setters/getters of properties, obvious comments (like `this getter returns field`) are optional. Note that Kotlin generates simple `get/set` methods under the hood. +* For setters/getters of properties, obvious comments (like \textbf{this getter returns field}) are optional. Note that Kotlin generates simple \textbf{get/set} methods under the hood. @@ -1089,7 +1108,7 @@ \subsubsection*{\textbf{2.1.2 Describing methods that have arguments}} /** * This is the short overview comment for the example interface. * / * Add a blank line between the comment text and each KDoc tag underneath * / - * @since 2019-01-01 + * @since 1.6 */ protected abstract class Sample { /** @@ -1132,29 +1151,35 @@ \subsubsection*{\textbf{2.1.3 Only one space between the Kdoc tag and content. T - Functional and technical description, explaining the principles, intentions, contracts, API, etc. -- The function description and @tags (`implSpec`, `apiNote`, and `implNote`) require an empty line after them. +- The function description and @tags (\textbf{implSpec}, \textbf{apiNote}, and \textbf{implNote}) require an empty line after them. -- `@implSpec`: A specification related to API implementation, and it should let the implementer decide whether to override it. +- \textbf{@implSpec}: A specification related to API implementation, and it should let the implementer decide whether to override it. -- `@apiNote`: Explain the API precautions, including whether to allow null and whether the method is thread-safe, as well as the algorithm complexity, input, and output range, exceptions, etc. +- \textbf{@apiNote}: Explain the API precautions, including whether to allow null and whether the method is thread-safe, as well as the algorithm complexity, input, and output range, exceptions, etc. -- `@implNote`: A note related to API implementation, which implementers should keep in mind. +- \textbf{@implNote}: A note related to API implementation, which implementers should keep in mind. -- One empty line, followed by regular `@param`, `@return`, `@throws`, and other comments. +- One empty line, followed by regular \textbf{@param}, \textbf{@return}, \textbf{@throws}, and other comments. -- The conventional standard "block labels" are arranged in the following order: `@param`, `@return`, `@throws`. +- The conventional standard "block labels" are arranged in the following order: \textbf{@param}, \textbf{@return}, \textbf{@throws}. Kdoc should not contain: - Empty descriptions in tag blocks. It is better not to write Kdoc than waste code line space. -- There should be no empty lines between the method/class declaration and the end of Kdoc (`*/` symbols). +- There should be no empty lines between the method/class declaration and the end of Kdoc (\textbf{*/} symbols). -Important note: KDoc does not support the `@deprecated` tag. Instead, use the `@Deprecated` annotation. +- \textbf{@author} tag. It doesn't matter who originally created a class when you can use \textbf{git blame} or VCS of your choice to look through the changes history. - +Important notes: -If a tag block cannot be described in one line, indent the content of the new line by \textit{four spaces} from the `@` position to achieve alignment (`@` counts as one + three spaces). +- KDoc does not support the \textbf{@deprecated} tag. Instead, use the \textbf{@Deprecated} annotation. + +- The \textbf{@since} tag should be used for versions only. Do not use dates in \textbf{@since} tag, it's confusing and less accurate. + + + +If a tag block cannot be described in one line, indent the content of the new line by \textit{four spaces} from the \textbf{@} position to achieve alignment (\textbf{@} counts as one + three spaces). @@ -1170,7 +1195,7 @@ \subsubsection*{\textbf{2.1.3 Only one space between the Kdoc tag and content. T In Kotlin, compared to Java, you can put several classes inside one file, so each class should have a Kdoc formatted comment (as stated in rule 2.1). -This comment should contain @since tag. The right style is to write the application version when its functionality is released. It should be entered after the `@since` tag. +This comment should contain @since tag. The right style is to write the application version when its functionality is released. It should be entered after the \textbf{@since} tag. @@ -1208,41 +1233,66 @@ \subsection*{\textbf{2.2 Adding comments on the file header}} This section describes the general rules of adding comments on the file header. -Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. +\subsubsection*{\textbf{2.2.1 Formatting of comments in the file header}} +\leavevmode\newline + +\label{sec:2.2.1} + -Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). Also, describe the content inside files that contain multiple or no classes. + +Comments on the file header should be placed before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. -Place comments on the file header before the package name and imports. If you need to add more content to the comment, subsequently add it in the same format. +Comments on the file header must include copyright information, without the creation date and author's name (use VCS for history management). + +Also, describe the content inside files that contain multiple or no classes. The following examples for Huawei describe the format of the \textit{copyright license}: \ -Chinese version: `版权所有 (c) 华为技术有限公司 2012-2020` \ +Chinese version: \textbf{版权所有 (c) 华为技术有限公司 2012-2020} \ + +English version: \textbf{Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved.} -English version: `Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved.` +\textbf{2012} and \textbf{2020} are the years the file was first created and the current year, respectively. -Regarding the \textbf{release notes}, see examples below: +Do not place \textbf{release notes} in header, use VCS to keep track of changes in file. Notable changes can be marked in individual KDocs using \textbf{@since} tag with version. -- `2012-2020` can be modified according to your actual situation. `2012` and `2020` are the years the file was first created and last modified, respectively. +Invalid example: -These two years can be the same (for example, `2020–2020`). When the file is substantially changed (for example, through feature extensions and major refactorings), the subsequent years must be updated. +\begin{lstlisting}[language=Kotlin] +/** + * Release notes: + * 2019-10-11: added class Foo + */ +class Foo +\end{lstlisting} + + +Valid example: + +\begin{lstlisting}[language=Kotlin] +/** + * @since 2.4.0 + */ +class Foo +\end{lstlisting} - The \textbf{copyright statement} can use your company's subsidiaries, as shown in the below examples: \ -Chinese version: `版权所有 (c) 海思半导体 2012-2020` \ +Chinese version: \textbf{版权所有 (c) 海思半导体 2012-2020} \ -English version: `Copyright (c) Hisilicon Technologies Co., Ltd. 2012-2020. All rights reserved.` +English version: \textbf{Copyright (c) Hisilicon Technologies Co., Ltd. 2012-2020. All rights reserved.} @@ -1261,13 +1311,13 @@ \subsection*{\textbf{2.2 Adding comments on the file header}} The following factors should be considered when writing the file header or comments for top-level classes: -- File header comments must start from the top of the file. If it is a top-level file comment, there should be a blank line after the last Kdoc `*/` symbol. If it is a comment for a top-level class, the class declaration should start immediately without using a newline. +- File header comments must start from the top of the file. If it is a top-level file comment, there should be a blank line after the last Kdoc \textbf{*/} symbol. If it is a comment for a top-level class, the class declaration should start immediately without using a newline. - Maintain a unified format. The specific format can be formulated by the project (for example, if you use an existing opensource project), and you need to follow it. - A top-level file-Kdoc must include a copyright and functional description, especially if there is more than one top-level class. -- Do not include empty comment blocks. If there is no content after the option `@apiNote`, the entire tag block should be deleted. +- Do not include empty comment blocks. If there is no content after the option \textbf{@apiNote}, the entire tag block should be deleted. - The industry practice is not to include historical information in the comments. The corresponding history can be found in VCS (git, svn, etc.). Therefore, it is not recommended to include historical data in the comments of the Kotlin source code. @@ -1365,11 +1415,11 @@ \subsubsection*{\textbf{2.4.1 Add a blank line between the body of the comment a - Leave one single space between the comment on the right side of the code and the code. -If you use conditional comments in the `if-else-if` scenario, put the comments inside the `else-if` branch or in the conditional block, but not before the `else-if`. This makes the code more understandable. +If you use conditional comments in the \textbf{if-else-if} scenario, put the comments inside the \textbf{else-if} branch or in the conditional block, but not before the \textbf{else-if}. This makes the code more understandable. When the if-block is used with curly braces, the comment should be placed on the next line after opening the curly braces. -Compared to Java, the `if` statement in Kotlin statements returns a value. For this reason, a comment block can describe a whole `if-statement`. +Compared to Java, the \textbf{if} statement in Kotlin statements returns a value. For this reason, a comment block can describe a whole \textbf{if-statement}. @@ -1401,7 +1451,7 @@ \subsubsection*{\textbf{2.4.1 Add a blank line between the body of the comment a \end{lstlisting} -- Start all comments (including KDoc) with a space after the first symbol (`//`, `/*`, `/**` and `*`) +- Start all comments (including KDoc) with a space after the first symbol (\textbf{//}, \textbf{/*}, \textbf{/} and \textbf{*}) @@ -1444,7 +1494,7 @@ \subsubsection*{\textbf{2.4.3 Code delivered to the client should not contain TO The code officially delivered to the client typically should not contain TODO/FIXME comments. -`TODO` comments are typically used to describe modification points that need to be improved and added. For example, refactoring FIXME comments are typically used to describe known defects and bugs that will be subsequently fixed and are not critical for an application. +\textbf{TODO} comments are typically used to describe modification points that need to be improved and added. For example, refactoring FIXME comments are typically used to describe known defects and bugs that will be subsequently fixed and are not critical for an application. They should all have a unified style to facilitate unified text search processing. @@ -1500,7 +1550,7 @@ \subsubsection*{\textbf{3.1.2 Code blocks in the source file should be separated 1. Kdoc for licensed or copyrighted files -2. `@file` annotation +2. \textbf{@file} annotation 3. Package name @@ -1516,11 +1566,11 @@ \subsubsection*{\textbf{3.1.2 Code blocks in the source file should be separated -c) Import statements are alphabetically arranged, without using line breaks and wildcards ( wildcard imports - `*`). +c) Import statements are alphabetically arranged, without using line breaks and wildcards ( wildcard imports - \textbf{*}). -d) \textbf{Recommendation}: One `.kt` source file should contain only one class declaration, and its name should match the filename +d) \textbf{Recommendation}: One \textbf{.kt} source file should contain only one class declaration, and its name should match the filename @@ -1587,7 +1637,7 @@ \subsubsection*{\textbf{3.1.4 Order of declaration parts of class-like code stru 2. Properties with comments/Kdoc should be separated by a newline before the comment/Kdoc. -3. Enum entries and constant properties (`const val`) in companion objects should be alphabetically arranged. +3. Enum entries and constant properties (\textbf{const val}) in companion objects should be alphabetically arranged. @@ -1613,7 +1663,7 @@ \subsubsection*{\textbf{3.1.4 Order of declaration parts of class-like code stru \textbf{Exception:} -All variants of a `(private) val` logger should be placed at the beginning of the class (`(private) val log`, `LOG`, `logger`, etc.). +All variants of a \textbf{(private) val} logger should be placed at the beginning of the class (\textbf{(private) val log}, \textbf{LOG}, \textbf{logger}, etc.). @@ -1630,7 +1680,7 @@ \subsubsection*{\textbf{3.2.1 Using braces in conditional statements and loop bl -Braces should always be used in `if`, `else`, `for`, `do`, and `while` statements, even if the program body is empty or contains only one statement. In special Kotlin `when` statements, you do not need to use braces for single-line statements. +Braces should always be used in \textbf{if}, \textbf{else}, \textbf{for}, \textbf{do}, and \textbf{while} statements, even if the program body is empty or contains only one statement. In special Kotlin \textbf{when} statements, you do not need to use braces for single-line statements. @@ -1647,7 +1697,7 @@ \subsubsection*{\textbf{3.2.1 Using braces in conditional statements and loop bl CLASS -> checkClassElements(node) } \end{lstlisting} -\textbf{Exception:} The only exception is ternary operator in Kotlin (a single line `if () <> else <>` ) +\textbf{Exception:} The only exception is ternary operator in Kotlin (a single line \textbf{if () <> else <>} ) @@ -1694,7 +1744,7 @@ \subsubsection*{\textbf{3.2.2 Opening braces are placed at the end of the line - The closing brace is on its own new line. -- The closing brace can be followed by a newline character. The only exceptions are `else`, `finally`, and `while` (from `do-while` statement), or `catch` keywords. +- The closing brace can be followed by a newline character. The only exceptions are \textbf{else}, \textbf{finally}, and \textbf{while} (from \textbf{do-while} statement), or \textbf{catch} keywords. These keywords should not be split from the closing brace by a newline character. @@ -1704,7 +1754,7 @@ \subsubsection*{\textbf{3.2.2 Opening braces are placed at the end of the line -1) For lambdas, there is no need to put a newline character after the first (function-related) opening brace. A newline character should appear only after an arrow (`->`) (see [point 5 of Rule 3.6.2]). +1) For lambdas, there is no need to put a newline character after the first (function-related) opening brace. A newline character should appear only after an arrow (\textbf{->}) (see [point 5 of Rule 3.6.2]). @@ -1715,7 +1765,7 @@ \subsubsection*{\textbf{3.2.2 Opening braces are placed at the end of the line \end{lstlisting} -2) for `else`/`catch`/`finally`/`while` (from `do-while` statement) keywords closing brace should stay on the same line: +2) for \textbf{else}/\textbf{catch}/\textbf{finally}/\textbf{while} (from \textbf{do-while} statement) keywords closing brace should stay on the same line: \begin{lstlisting}[language=Kotlin] do { @@ -1765,7 +1815,7 @@ \subsection*{\textbf{3.3 Indentation}} -Only spaces are permitted for indentation, and each indentation should equal `four spaces` (tabs are not permitted). +Only spaces are permitted for indentation, and each indentation should equal \textbf{four spaces} (tabs are not permitted). If you prefer using tabs, simply configure them to change to spaces in your IDE automatically. @@ -1773,7 +1823,7 @@ \subsection*{\textbf{3.3 Indentation}} - The code block is placed immediately after an opening brace. -- The code block is placed after each operator, including the assignment operator (`+`/`-`/`\&\&`/`=`/etc.) +- The code block is placed after each operator, including the assignment operator (\textbf{+}/\textbf{-}/\textbf{\&\&}/\textbf{=}/etc.) - The code block is a call chain of methods: @@ -1821,7 +1871,7 @@ \subsection*{\textbf{3.3 Indentation}} -\textbf{Note:} there should be an indentation after all statements such as `if`, `for`, etc. However, according to this code style, such statements require braces. +\textbf{Note:} there should be an indentation after all statements such as \textbf{if}, \textbf{for}, etc. However, according to this code style, such statements require braces. @@ -1833,7 +1883,7 @@ \subsection*{\textbf{3.3 Indentation}} \textbf{Exceptions}: -- When breaking the parameter list of a method/class constructor, it can be aligned with `8 spaces`. A parameter that was moved to a new line can be on the same level as the previous argument: +- When breaking the parameter list of a method/class constructor, it can be aligned with \textbf{8 spaces}. A parameter that was moved to a new line can be on the same level as the previous argument: @@ -1848,7 +1898,7 @@ \subsection*{\textbf{3.3 Indentation}} \end{lstlisting} -- Such operators as `+`/`-`/`*` can be indented with `8 spaces`: +- Such operators as \textbf{+}/\textbf{-}/\textbf{*} can be indented with \textbf{8 spaces}: @@ -1858,7 +1908,7 @@ \subsection*{\textbf{3.3 Indentation}} \end{lstlisting} -- A list of supertypes should be indented with `4 spaces` if they are on different lines or with `8 spaces` if the leading colon is also on a separate line +- A list of supertypes should be indented with \textbf{4 spaces} if they are on different lines or with \textbf{8 spaces} if the leading colon is also on a separate line @@ -1878,7 +1928,7 @@ \subsection*{\textbf{3.4 Empty blocks}} -Avoid empty blocks, and ensure braces start on a new line. An empty code block can be closed immediately on the same line and the next line. However, a newline is recommended between opening and closing braces `{}` (see the examples below.) +Avoid empty blocks, and ensure braces start on a new line. An empty code block can be closed immediately on the same line and the next line. However, a newline is recommended between opening and closing braces \textbf{\{\}} (see the examples below.) @@ -1929,7 +1979,7 @@ \subsection*{\textbf{3.5 Line length}} -Line length should be less than 120 symbols. The international code style prohibits `non-Latin` (`non-ASCII`) symbols. +Line length should be less than 120 symbols. The international code style prohibits \textbf{non-Latin} (\textbf{non-ASCII}) symbols. (See [Identifiers]) However, if you still intend on using them, follow the following convention: @@ -1941,15 +1991,15 @@ \subsection*{\textbf{3.5 Line length}} Typically, narrow characters are also called "half-width" characters. -All characters in the ASCII character set include letters (such as `a, A`), numbers (such as `0, 3`), and punctuation spaces (such as `,` , `{`), all of which are narrow characters. +All characters in the ASCII character set include letters (such as \textbf{a, A}), numbers (such as \textbf{0, 3}), and punctuation spaces (such as \textbf{,} , \textbf{\{}), all of which are narrow characters. -Wide characters are also called "full-width" characters. Chinese characters (such as `中, 文`), Chinese punctuation (`,` , `;` ), full-width letters and numbers (such as `A、3`) are "full-width" characters. +Wide characters are also called "full-width" characters. Chinese characters (such as \textbf{中, 文}), Chinese punctuation (\textbf{,} , \textbf{;} ), full-width letters and numbers (such as \textbf{A、3}) are "full-width" characters. Each one of these characters represents two narrow characters. -- Any line that exceeds this limit (`120 narrow symbols`) should be wrapped, as described in the [Newline section]. +- Any line that exceeds this limit (\textbf{120 narrow symbols}) should be wrapped, as described in the [Newline section]. @@ -1959,7 +2009,7 @@ \subsection*{\textbf{3.5 Line length}} 1. The long URL or long JSON method reference in KDoc. -2. The `package` and `import` statements. +2. The \textbf{package} and \textbf{import} statements. 3. The command line in the comment, enabling it to be cut and pasted into the shell for use. @@ -1976,7 +2026,7 @@ \subsubsection*{\textbf{3.6.1 Each line can have a maximum of one statement}} \label{sec:3.6.1} -Each line can have a maximum of one code statement. This recommendation prohibits the use of code with `;` because it decreases code visibility. +Each line can have a maximum of one code statement. This recommendation prohibits the use of code with \textbf{;} because it decreases code visibility. @@ -2002,19 +2052,19 @@ \subsubsection*{\textbf{3.6.2 Rules for line-breaking}} -1) Unlike Java, Kotlin allows you not to put a semicolon (`;`) after each statement separated by a newline character. +1) Unlike Java, Kotlin allows you not to put a semicolon (\textbf{;}) after each statement separated by a newline character. There should be no redundant semicolon at the end of the lines. -When a newline character is needed to split the line, it should be placed after such operators as `\&\&`/`||`/`+`/etc. and all infix functions (for example, `xor`). +When a newline character is needed to split the line, it should be placed after such operators as \textbf{\&\&}/\textbf{||}/\textbf{+}/etc. and all infix functions (for example, \textbf{xor}). -However, the newline character should be placed before operators such as `.`, `?.`, `?:`, and `::`. +However, the newline character should be placed before operators such as \textbf{.}, \textbf{?.}, \textbf{?:}, and \textbf{::}. -Note that all comparison operators, such as `==`, `>`, `<`, should not be split. +Note that all comparison operators, such as \textbf{==}, \textbf{>}, \textbf{<}, should not be split. @@ -2035,7 +2085,7 @@ \subsubsection*{\textbf{3.6.2 Rules for line-breaking}} \end{lstlisting} -\textbf{Note:} You need to follow the functional style, meaning each function call in a chain with `.` should start at a new line if the chain of functions contains more than one call: +\textbf{Note:} You need to follow the functional style, meaning each function call in a chain with \textbf{.} should start at a new line if the chain of functions contains more than one call: \begin{lstlisting}[language=Kotlin] val value = otherValue!! @@ -2046,7 +2096,7 @@ \subsubsection*{\textbf{3.6.2 Rules for line-breaking}} } .size \end{lstlisting} -\textbf{Note:} The parser prohibits the separation of the `!!` operator from the value it is checking. +\textbf{Note:} The parser prohibits the separation of the \textbf{!!} operator from the value it is checking. @@ -2061,17 +2111,17 @@ \subsubsection*{\textbf{3.6.2 Rules for line-breaking}} \end{lstlisting} -2) Newlines should be placed after the assignment operator (`=`). +2) Newlines should be placed after the assignment operator (\textbf{=}). -3) In function or class declarations, the name of a function or constructor should not be split by a newline from the opening brace `(`. +3) In function or class declarations, the name of a function or constructor should not be split by a newline from the opening brace \textbf{(}. A brace should be placed immediately after the name without any spaces in declarations or at call sites. -4) Newlines should be placed right after the comma (`,`). +4) Newlines should be placed right after the comma (\textbf{,}). 5) If a lambda statement contains more than one line in its body, a newline should be placed after an arrow if the lambda statement has explicit parameters. - If it uses an implicit parameter (`it`), the newline character should be placed after the opening brace (`{`). + If it uses an implicit parameter (\textbf{it}), the newline character should be placed after the opening brace (\textbf{\{}). The following examples illustrate this rule: @@ -2188,7 +2238,7 @@ \subsection*{\textbf{3.7 Using blank lines}} Reduce unnecessary blank lines and maintain a compact code size. By reducing unnecessary blank lines, you can display more code on one screen, which improves code readability. -- Blank lines should separate content based on relevance and should be placed between groups of fields, constructors, methods, nested classes, `init` blocks, and objects (see [3.1.2]). +- Blank lines should separate content based on relevance and should be placed between groups of fields, constructors, methods, nested classes, \textbf{init} blocks, and objects (see [3.1.2]). - Do not use more than one line inside methods, type definitions, and initialization expressions. @@ -2216,7 +2266,7 @@ \subsection*{\textbf{3.8 Horizontal space}} This section describes general rules and recommendations for using spaces in the code. -\subsubsection*{\textbf{3.8.1}} +\subsubsection*{\textbf{3.8.1: Usage of whitespace for code separation}} \leavevmode\newline \label{sec:3.8.1} @@ -2231,19 +2281,19 @@ \subsubsection*{\textbf{3.8.1}} -1. Separate keywords (such as `if`, `when`, `for`) from the opening parenthesis with single whitespace. +1. Separate keywords (such as \textbf{if}, \textbf{when}, \textbf{for}) from the opening parenthesis with single whitespace. - The only exception is the `constructor` keyword, which should not be separated from the opening parenthesis. + The only exception is the \textbf{constructor} keyword, which should not be separated from the opening parenthesis. -2. Separate keywords like `else` or `try` from the opening brace (`{`) with single whitespace. +2. Separate keywords like \textbf{else} or \textbf{try} from the opening brace (\textbf{\{}) with single whitespace. - If `else` is used in a ternary-style statement without braces, there should be a single space between `else` and the statement after: `if (condition) foo() else bar()` + If \textbf{else} is used in a ternary-style statement without braces, there should be a single space between \textbf{else} and the statement after: \textbf{if (condition) foo() else bar()} -3. Use a \textbf{single} whitespace before all opening braces (`{`). The only exception is the passing of a lambda as a parameter inside parentheses: +3. Use a \textbf{single} whitespace before all opening braces (\textbf{\{}). The only exception is the passing of a lambda as a parameter inside parentheses: \begin{lstlisting}[language=Kotlin] private fun foo(a: (Int) -> Int, b: Int) {} @@ -2257,9 +2307,9 @@ \subsubsection*{\textbf{3.8.1}} - - A colon in generic structures with the `where` keyword: `where T : Type` + - A colon in generic structures with the \textbf{where} keyword: \textbf{where T : Type} - - Arrow in lambdas: `(str: String) -> str.length()` + - Arrow in lambdas: \textbf{(str: String) -> str.length()} @@ -2267,33 +2317,33 @@ \subsubsection*{\textbf{3.8.1}} -- Two colons (`::`) are written without spaces:\ +- Two colons (\textbf{::}) are written without spaces:\ - `Object::toString` + \textbf{Object::toString} -- The dot separator (`.`) that stays on the same line with an object name:\ +- The dot separator (\textbf{.}) that stays on the same line with an object name:\ - `object.toString()` + \textbf{object.toString()} -- Safe access modifiers `?.` and `!!` that stay on the same line with an object name:\ +- Safe access modifiers \textbf{?.} and \textbf{!!} that stay on the same line with an object name:\ - `object?.toString()` + \textbf{object?.toString()} -- Operator `..` for creating ranges:\ +- Operator \textbf{..} for creating ranges:\ - `1..100` + \textbf{1..100} -5. Use spaces after (`,`), (`:`), and (`;`), except when the symbol is at the end of the line. +5. Use spaces after (\textbf{,}), (\textbf{:}), and (\textbf{;}), except when the symbol is at the end of the line. - However, note that this code style prohibits the use of (`;`) in the middle of a line ([see 3.3.2]). + However, note that this code style prohibits the use of (\textbf{;}) in the middle of a line ([see 3.3.2]). There should be no whitespaces at the end of a line. - The only scenario where there should be no space after a colon is when the colon is used in the annotation to specify a use-site target (for example, `@param:JsonProperty`). + The only scenario where there should be no space after a colon is when the colon is used in the annotation to specify a use-site target (for example, \textbf{@param:JsonProperty}). - There should be no spaces before `,` , `:` and `;`. + There should be no spaces before \textbf{,} , \textbf{:} and \textbf{;}. @@ -2301,7 +2351,7 @@ \subsubsection*{\textbf{3.8.1}} - - When `:` is used to separate a type and a supertype, including an anonymous object (after object keyword) + - When \textbf{:} is used to separate a type and a supertype, including an anonymous object (after object keyword) - When delegating to a superclass constructor or different constructor of the same class @@ -2320,19 +2370,19 @@ \subsubsection*{\textbf{3.8.1}} \end{lstlisting} -6. There should be \textit{only one space} between the identifier and its type: `list: List` +6. There should be \textit{only one space} between the identifier and its type: \textbf{list: List} -If the type is nullable, there should be no space before `?`. +If the type is nullable, there should be no space before \textbf{?}. -7. When using `[]` operator (`get/set`) there should be \textbf{no} spaces between identifier and `[` : `someList[0]`. +7. When using \textbf{[]} operator (\textbf{get/set}) there should be \textbf{no} spaces between identifier and \textbf{[} : \textbf{someList[0]}. 8. There should be no space between a method or constructor name (both at declaration and at call site) and a parenthesis: - `foo() {}`. Note that this sub-rule is related only to spaces; the rules for whitespaces are described in [see 3.6.2]. + \textbf{foo() \{\}}. Note that this sub-rule is related only to spaces; the rules for whitespaces are described in [see 3.6.2]. This rule does not prohibit, for example, the following code: @@ -2344,15 +2394,15 @@ \subsubsection*{\textbf{3.8.1}} \end{lstlisting} -9. Never put a space after `(`, `[`, `<` (when used as a bracket in templates) or before `)`, `]`, `>` (when used as a bracket in templates). +9. Never put a space after \textbf{(}, \textbf{[}, \textbf{<} (when used as a bracket in templates) or before \textbf{)}, \textbf{]}, \textbf{>} (when used as a bracket in templates). -10. There should be no spaces between a prefix/postfix operator (like `!!` or `++`) and its operand. +10. There should be no spaces between a prefix/postfix operator (like \textbf{!!} or \textbf{++}) and its operand. -\subsubsection*{\textbf{3.8.2}} +\subsubsection*{\textbf{3.8.2: No spaces for horizontal alignment}} \leavevmode\newline \label{sec:3.8.2} @@ -2371,7 +2421,7 @@ \subsubsection*{\textbf{3.8.2}} -Recommendation: Alignment only looks suitable for `enum class`, where it can be used in table format to improve code readability: +Recommendation: Alignment only looks suitable for \textbf{enum class}, where it can be used in table format to improve code readability: \begin{lstlisting}[language=Kotlin] enum class Warnings(private val id: Int, private val canBeAutoCorrected: Boolean, private val warn: String) : Rule { @@ -2407,7 +2457,7 @@ \subsection*{\textbf{3.9 Enumerations}} -1) The comma and line break characters separate enum values. Put `;` on the new line: +1) The comma and line break characters separate enum values. Put \textbf{;} on the new line: \begin{lstlisting}[language=Kotlin] enum class Warnings { @@ -2453,7 +2503,7 @@ \subsection*{\textbf{3.9 Enumerations}} - The variable value only changes within a fixed range and is defined with the enum type. -- Avoid comparison with magic numbers of `-1, 0, and 1`; use enums instead. +- Avoid comparison with magic numbers of \textbf{-1, 0, and 1}; use enums instead. @@ -2510,13 +2560,13 @@ \subsection*{\textbf{3.11 'When' expression}} -The `when` statement must have an 'else' branch unless the condition variable is enumerated or a sealed type. +The \textbf{when} statement must have an 'else' branch unless the condition variable is enumerated or a sealed type. -Each `when` statement should contain an `else` statement group, even if it does not contain any code. +Each \textbf{when} statement should contain an \textbf{else} statement group, even if it does not contain any code. -\textbf{Exception:} If 'when' statement of the `enum or sealed` type contains all enum values, there is no need to have an "else" branch. +\textbf{Exception:} If 'when' statement of the \textbf{enum or sealed} type contains all enum values, there is no need to have an "else" branch. The compiler can issue a warning when it is missing. @@ -2590,7 +2640,7 @@ \subsection*{\textbf{3.13 Block comments}} \end{lstlisting} -\textbf{Note}: Use `/*...*/` block comments to enable automatic formatting by IDEs. +\textbf{Note}: Use \textbf{/*...*/} block comments to enable automatic formatting by IDEs. @@ -2634,7 +2684,7 @@ \subsubsection*{\textbf{3.14.1 Declaration with multiple modifiers}} \end{lstlisting} -\subsubsection*{\textbf{3.14.2}} +\subsubsection*{\textbf{3.14.2: Separate long numerical values with an underscore}} \leavevmode\newline \label{sec:3.14.2} @@ -2753,7 +2803,7 @@ \subsubsection*{\textbf{4.1.1 Do not use Float and Double types when accurate ca Floating-point numbers provide a good approximation over a wide range of values, but they cannot produce accurate results in some cases. -Binary floating-point numbers are unsuitable for precise calculations because it is impossible to represent 0.1 or any other negative power of 10 in a `binary representation` with a finite length. +Binary floating-point numbers are unsuitable for precise calculations because it is impossible to represent 0.1 or any other negative power of 10 in a \textbf{binary representation} with a finite length. @@ -2765,13 +2815,13 @@ \subsubsection*{\textbf{4.1.1 Do not use Float and Double types when accurate ca \end{lstlisting} -However, it will print the following value: `0.8999999999999999` +However, it will print the following value: \textbf{0.8999999999999999} -Therefore, for precise calculations (for example, in finance or exact sciences), using such types as `Int`, `Long`, `BigDecimal`are recommended. +Therefore, for precise calculations (for example, in finance or exact sciences), using such types as \textbf{Int}, \textbf{Long}, \textbf{BigDecimal}are recommended. -The `BigDecimal` type should serve as a good choice. +The \textbf{BigDecimal} type should serve as a good choice. @@ -2793,12 +2843,12 @@ \subsubsection*{\textbf{4.1.1 Do not use Float and Double types when accurate ca \end{lstlisting} -\subsubsection*{\textbf{4.1.2}} +\subsubsection*{\textbf{4.1.2: Comparing numeric float type values}} \leavevmode\newline \label{sec:4.1.2} -Numeric float type values should not be directly compared with the equality operator (==) or other methods, such as `compareTo()` and `equals()`. Since floating-point numbers involve precision problems in computer representation, it is better to use `BigDecimal` as recommended in [Rule 4.1.1] to make accurate computations and comparisons. The following code describes these problems. +Numeric float type values should not be directly compared with the equality operator (==) or other methods, such as \textbf{compareTo()} and \textbf{equals()}. Since floating-point numbers involve precision problems in computer representation, it is better to use \textbf{BigDecimal} as recommended in [Rule 4.1.1] to make accurate computations and comparisons. The following code describes these problems. @@ -2845,13 +2895,13 @@ \subsubsection*{\textbf{4.1.3 Try to use 'val' instead of 'var' for variable dec -Variables with the `val` modifier are immutable (read-only). +Variables with the \textbf{val} modifier are immutable (read-only). -Using `val` variables instead of `var` variables increases code robustness and readability. +Using \textbf{val} variables instead of \textbf{var} variables increases code robustness and readability. -This is because `var` variables can be reassigned several times in the business logic. +This is because \textbf{var} variables can be reassigned several times in the business logic. -However, in some scenarios with loops or accumulators, only `var`s are permitted. +However, in some scenarios with loops or accumulators, only \textbf{var}s are permitted. @@ -2861,7 +2911,7 @@ \subsection*{\textbf{4.2 Types}} This section provides recommendations for using types. -\subsubsection*{\textbf{4.2.1}} +\subsubsection*{\textbf{4.2.1: Use Contracts and smart cast as much as possible}} \leavevmode\newline \label{sec:4.2.1} @@ -2892,7 +2942,7 @@ \subsubsection*{\textbf{4.2.1}} Also, Kotlin 1.3 introduced \href{https://kotlinlang.org/docs/reference/whatsnew13.html#contracts}{Contracts} that provide enhanced logic for smart-cast. -Contracts are used and are very stable in `stdlib`, for example: +Contracts are used and are very stable in \textbf{stdlib}, for example: @@ -2931,7 +2981,7 @@ \subsubsection*{\textbf{4.2.1}} \end{lstlisting} -\subsubsection*{\textbf{4.2.2}} +\subsubsection*{\textbf{4.2.2: Try to use type alias to represent types making code more readable}} \leavevmode\newline \label{sec:4.2.2} @@ -2942,9 +2992,9 @@ \subsubsection*{\textbf{4.2.2}} If the type name is too long, you can replace it with a shorter name, which helps to shorten long generic types. -For example, code looks much more readable if you introduce a `typealias` instead of a long chain of nested generic types. +For example, code looks much more readable if you introduce a \textbf{typealias} instead of a long chain of nested generic types. -We recommend using a `typealias` if the type contains \textbf{more than two} nested generic types and is longer than \textbf{25 chars}. +We recommend using a \textbf{typealias} if the type contains \textbf{more than two} nested generic types and is longer than \textbf{25 chars}. @@ -2980,12 +3030,12 @@ \subsection*{\textbf{4.3 Null safety and variable declarations}} -\subsubsection*{\textbf{4.3.1}} +\subsubsection*{\textbf{4.3.1: Avoid declaring variables with nullable types}} \leavevmode\newline \label{sec:4.3.1} -To avoid `NullPointerException` and help the compiler prevent Null Pointer Exceptions, avoid using nullable types (with `?` symbol). +To avoid \textbf{NullPointerException} and help the compiler prevent Null Pointer Exceptions, avoid using nullable types (with \textbf{?} symbol). @@ -3003,11 +3053,11 @@ \subsubsection*{\textbf{4.3.1}} \end{lstlisting} -Nevertheless, when using Java libraries extensively, you have to use nullable types and enrich the code with `!!` and `?` symbols. +Nevertheless, when using Java libraries extensively, you have to use nullable types and enrich the code with \textbf{!!} and \textbf{?} symbols. Avoid using nullable types for Kotlin stdlib (declared in \href{https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/}{official documentation}). -Try to use initializers for empty collections. For example, if you want to initialize a list instead of `null`, use `emptyList()`. +Try to use initializers for empty collections. For example, if you want to initialize a list instead of \textbf{null}, use \textbf{emptyList()}. @@ -3025,7 +3075,7 @@ \subsubsection*{\textbf{4.3.1}} \end{lstlisting} -\subsubsection*{\textbf{4.3.2}} +\subsubsection*{\textbf{4.3.2: Variables of generic types should have an explicit type declaration}} \leavevmode\newline \label{sec:4.3.2} @@ -3066,15 +3116,15 @@ \subsubsection*{\textbf{4.3.3 Null-safety}} -Try to avoid explicit null checks (explicit comparison with `null`) +Try to avoid explicit null checks (explicit comparison with \textbf{null}) Kotlin is declared as \href{https://kotlinlang.org/docs/reference/null-safety.html}{Null-safe} language. -However, Kotlin architects wanted Kotlin to be fully compatible with Java; that's why the `null` keyword was also introduced in Kotlin. +However, Kotlin architects wanted Kotlin to be fully compatible with Java; that's why the \textbf{null} keyword was also introduced in Kotlin. -There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c +There are several code-structures that can be used in Kotlin to avoid null-checks. For example: \textbf{?:}, \textbf{.let \{\}}, \textbf{.also \{\}}, e.t.c @@ -3143,7 +3193,7 @@ \subsubsection*{\textbf{4.3.3 Null-safety}} -In the case of complex expressions, such as multiple `else-if` structures or long conditional statements, there is common sense to use explicit comparison with `null`. +In the case of complex expressions, such as multiple \textbf{else-if} structures or long conditional statements, there is common sense to use explicit comparison with \textbf{null}. @@ -3165,7 +3215,7 @@ \subsubsection*{\textbf{4.3.3 Null-safety}} \end{lstlisting} -Please also note, that instead of using `require(a != null)` with a not null check - you should use a special Kotlin function called `requireNotNull(a)`. +Please also note, that instead of using \textbf{require(a != null)} with a not null check - you should use a special Kotlin function called \textbf{requireNotNull(a)}. @@ -3203,7 +3253,7 @@ \subsection*{\textbf{5.1 Function design}} Kotlin is \href{https://www.slideshare.net/abreslav/whos-more-functional-kotlin-groovy-scala-or-java}{designed} to support and encourage functional programming, featuring the corresponding built-in mechanisms. -Also, it supports standard collections and sequences feature methods that enable functional programming (for example, `apply`, `with`, `let`, and `run`), Kotlin Higher-Order functions, function types, lambdas, and default function arguments. +Also, it supports standard collections and sequences feature methods that enable functional programming (for example, \textbf{apply}, \textbf{with}, \textbf{let}, and \textbf{run}), Kotlin Higher-Order functions, function types, lambdas, and default function arguments. As [previously discussed], Kotlin supports and encourages the use of immutable types, which in turn motivates programmers to write pure functions that avoid side effects and have a corresponding output for specific input. @@ -3294,6 +3344,39 @@ \subsubsection*{\textbf{5.1.3 Avoid using nested functions}} println("Nested Output: ${nested()}") } \end{lstlisting} +\subsubsection*{\textbf{5.1.4 Negated function calls}} +\leavevmode\newline + +\label{sec:5.1.4} + +Don't use negated function calls if it can be replaced with negated version of this function + + + +\textbf{Invalid example}: + +\begin{lstlisting}[language=Kotlin] +fun foo() { + val list = listOf(1, 2, 3) + + if (!list.isEmpty()) { + // Some cool logic + } +} +\end{lstlisting} + + +\textbf{Valid example}: + +\begin{lstlisting}[language=Kotlin] +fun foo() { + val list = listOf(1, 2, 3) + + if (list.isNotEmpty()) { + // Some cool logic + } +} +\end{lstlisting} \subsection*{\textbf{5.2 Function arguments}} @@ -3335,7 +3418,7 @@ \subsubsection*{\textbf{5.2.2 Number of function parameters should be limited to -A long argument list is a \href{https://en.wikipedia.org/wiki/Code_smell}{code smell} that leads to less reliable code. +A long argument list is a \href{https://en.wikipedia.org/wiki/Code\_smell}{code smell} that leads to less reliable code. It is recommended to reduce the number of parameters. Having \textbf{more than five} parameters leads to difficulties in maintenance and conflicts merging. @@ -3426,9 +3509,9 @@ \subsubsection*{\textbf{6.1.2 Prefer data classes instead of classes without any \label{sec:6.1.2} -Some people say that the data class is a code smell. However, if you need to use it (which makes your code more simple), you can utilize the Kotlin `data class`. The main purpose of this class is to hold data, +Some people say that the data class is a code smell. However, if you need to use it (which makes your code more simple), you can utilize the Kotlin \textbf{data class}. The main purpose of this class is to hold data, -but also `data class` will automatically generate several useful methods: +but also \textbf{data class} will automatically generate several useful methods: - equals()/hashCode() pair; @@ -3440,7 +3523,7 @@ \subsubsection*{\textbf{6.1.2 Prefer data classes instead of classes without any -Therefore, instead of using `normal` classes: +Therefore, instead of using \textbf{normal} classes: @@ -3537,7 +3620,7 @@ \subsubsection*{\textbf{6.1.4 Do not use redundant init blocks in your class}} \label{sec:6.1.4} -Several init blocks are redundant and generally should not be used in your class. The primary constructor cannot contain any code. That is why Kotlin has introduced `init` blocks. +Several init blocks are redundant and generally should not be used in your class. The primary constructor cannot contain any code. That is why Kotlin has introduced \textbf{init} blocks. These blocks store the code to be run during the class initialization. @@ -3545,9 +3628,9 @@ \subsubsection*{\textbf{6.1.4 Do not use redundant init blocks in your class}} Even when you follow (rule 3.2)[\#r3.2], this makes your code less readable as the programmer needs to keep in mind all init blocks and trace the execution of the code. -Therefore, you should try to use a single `init` block to reduce the code's complexity. If you need to do some logging or make some calculations before the class property assignment, you can use powerful functional programming. This will reduce the possibility of the error if your `init` blocks' order is accidentally changed and +Therefore, you should try to use a single \textbf{init} block to reduce the code's complexity. If you need to do some logging or make some calculations before the class property assignment, you can use powerful functional programming. This will reduce the possibility of the error if your \textbf{init} blocks' order is accidentally changed and -make the code logic more coupled. It is always enough to use one `init` block to implement your idea in Kotlin. +make the code logic more coupled. It is always enough to use one \textbf{init} block to implement your idea in Kotlin. @@ -3584,9 +3667,9 @@ \subsubsection*{\textbf{6.1.4 Do not use redundant init blocks in your class}} \end{lstlisting} -The `init` block was not added to Kotlin to help you initialize your properties; it is needed for more complex tasks. +The \textbf{init} block was not added to Kotlin to help you initialize your properties; it is needed for more complex tasks. -Therefore if the `init` block contains only assignments of variables - move it directly to properties to be correctly initialized near the declaration. +Therefore if the \textbf{init} block contains only assignments of variables - move it directly to properties to be correctly initialized near the declaration. In some cases, this rule can be in clash with [6.1.1], but that should not stop you. @@ -3644,7 +3727,7 @@ \subsubsection*{\textbf{6.1.6 Abstract class should have at least one abstract m Abstract classes are used to force a developer to implement some of its parts in their inheritors. -When the abstract class has no abstract methods, it was set `abstract` incorrectly and can be converted to a regular class. +When the abstract class has no abstract methods, it was set \textbf{abstract} incorrectly and can be converted to a regular class. @@ -3700,7 +3783,7 @@ \subsubsection*{\textbf{6.1.7 When using the}} \end{lstlisting} -In this case, the name of the backing property (`_table`) should be the same as the name of the real property (`table`) but should have an underscore (`_`) prefix. +In this case, the name of the backing property (\textbf{\_table}) should be the same as the name of the real property (\textbf{table}) but should have an underscore (\textbf{\_}) prefix. It is one of the exceptions from the [identifier names rule] @@ -3713,7 +3796,7 @@ \subsubsection*{\textbf{6.1.8 Avoid using custom getters and setters}} Kotlin has a perfect mechanism of \href{https://kotlinlang.org/docs/reference/properties.html#properties-and-fields}{properties}. -Kotlin compiler automatically generates `get` and `set` methods for properties and can override them. +Kotlin compiler automatically generates \textbf{get} and \textbf{set} methods for properties and can override them. @@ -3732,11 +3815,11 @@ \subsubsection*{\textbf{6.1.8 Avoid using custom getters and setters}} \end{lstlisting} -From the callee code, these methods look like access to this property: `A().isEmpty = true` for setter and `A().isEmpty` for getter. +From the callee code, these methods look like access to this property: \textbf{A().isEmpty = true} for setter and \textbf{A().isEmpty} for getter. -However, when `get` and `set` are overridden, it isn't very clear for a developer who uses this particular class. +However, when \textbf{get} and \textbf{set} are overridden, it isn't very clear for a developer who uses this particular class. The developer expects to get the property value but receives some unknown value and some extra side-effect hidden by the custom getter/setter. @@ -3763,7 +3846,7 @@ \subsubsection*{\textbf{6.1.8 Avoid using custom getters and setters}} \end{lstlisting} -\textbf{Exception:} `Private setters` are only exceptions that are not prohibited by this rule. +\textbf{Exception:} \textbf{Private setters} are only exceptions that are not prohibited by this rule. @@ -3774,7 +3857,7 @@ \subsubsection*{\textbf{6.1.9 Never use the name of a variable in the custom get If you ignored [recommendation 6.1.8], be careful with using the name of the property in your custom getter/setter -as it can accidentally cause a recursive call and a `StackOverflow Error`. Use the `field` keyword instead. +as it can accidentally cause a recursive call and a \textbf{StackOverflow Error}. Use the \textbf{field} keyword instead. @@ -3948,7 +4031,7 @@ \subsection*{\textbf{6.3 Interfaces}} \label{sec:6.3} -An `Interface` in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. +An \textbf{Interface} in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. They can have properties, but these need to be abstract or to provide accessor implementations. diff --git a/wp/sections/compare.tex b/wp/sections/compare.tex index 015b1f36f3..3c6a18082e 100644 --- a/wp/sections/compare.tex +++ b/wp/sections/compare.tex @@ -1,77 +1,95 @@ \subsection{About ktlint} -\par Ktlint is a popular an anti-bikeshedding Kotlin linter with a built-in formatter created by pinterest. It tries to capture (reflect) official code style from kotlinlang.org and Android Kotlin Style Guide and then automatically apply these rules to your codebase. Ktlint checks and can automatically fix code and it claims to be simple and easy to use. As it is focused more on checking code-style and code-smell related issues, ktlint inspections are working with Abstract Syntax Tree generated by Kotlin parser. Ktlint framework has some basic utilities to make the work with Kotlin AST easier, but anyway all inspections work with original ASTNode. +Ktlint is a popular an anti-bikeshedding Kotlin linter with a built-in formatter created by Pinterest\footnote{\url{https://github.com/pinterest/}}. It tries to reflect official code style from \texttt{kotlinlang.org} and Android Kotlin Style Guide and then automatically apply these rules to your codebase. Ktlint can check and automatically fix code. It claims to be simple and easy to use. As it is focused more on checking code-style and code-smell related issues, ktlint inspections work with Abstract Syntax Tree generated by Kotlin parser. Ktlint framework has some basic utilities to make the work with Kotlin AST easier, but anyway all inspections work with original ASTNode provided by Kotlin parser. -Ktlint has been developing since 2016 and from then on it has 3.8k stars, 309 forks and 390 closed PRs (at least on the moment of writing this article). It looks to be the most popular and mature linter in the Kotlin community right now. There have been written ~15k lines of code. +Ktlint has been developed since 2016 and since then it has 3.8k stars, 309 forks and 390 closed PRs (2020). It looks to be the most popular and mature linter in the Kotlin community right now with approximately 15k lines of code written. -Ktlint has it’s own set of rules, which divides on standard and experimental rules. But unfortunately the number of fixers\&checkers in the standard ruleset is very few ($\approx$20 rules) and inspections are very trivial. +Ktlint has its own set of rules, which are divided on standard and experimental rules. But unfortunately the number of fixers and checkers in the standard ruleset is very few (~20 rules) and inspections are trivial. -Ktlint can be used as a plugin via Maven, Gradle or command line app. To configure rules in Ktlint you should modify .editorconfig file - this is the only configuration that ktlint provides. Actually you even can’t configure specific rules (for example to disable or suppress any of them), instead you can provide some common settings like the number of spaces for indenting. In other words, ktlint has a ”fixed hardcoded” codestyle that is not very configurable. Properties should be specified under $.kt,kts$. +Ktlint can be used as a plugin for Maven, Gradle or command line app. \texttt{.editorconfig} file should be modified to configure rules. This is the - only configuration that ktlint provides and it contains just simple configuration like the number of spaces in indents. Actually user even can’t configure specific rules (for example to disable or suppress any of them), instead you can provide some common settings like the number of spaces for indenting. In other words, ktlint has a "fixed hardcoded” code-style that is not very configurable. -If you want to implement your own rules you need to create a your own ruleset. Ktlint is very user-friendly for creation of custom rulesets. In this case ktlint will parse the code using a Kotlin parser and will trigger your inspection (as visitor) for each and every node of AST. Ktlint is using java’s ServiceLoader to discover all available ”RuleSets”. ServiceLoader is used to inject your own implementation of rules for the static analysis. In this case ktlint becomes a third-party dependency and a framework. Basically you should provide implementation of RuleSetProviderinterface. +If you want to implement your own rules you need to create a your own Ruleset. Ktlint is very user-friendly for creation of custom Rulesets. In this case ktlint will parse the code using a Kotlin parser and will trigger your inspection (as visitor) for each node of AST. Ktlint uses javas \texttt{ServiceLoader} to discover all available Rulesets. \texttt{ServiceLoader} is used to inject your own implementation of rules for the static analysis. In this case ktlint becomes both a third-party dependency and a framework. Basically you should provide implementation of \texttt{RuleSetProvider} interface. -Ktlint refers to article on medium on how to create a ruleset and a rule. +Ktlint refers to article on Medium\footnote{\url{https://medium.com/mydevnotes/ktlint-improve-your-kotlin-code-quality-with-lint-checks-13a4456c4600}} on how to create a custom Ruleset and a Rule. -\par A lot of projects uses ktlint as their code formatting tool. For example, OmiseGo \footnote{\url{https://github.com/omgnetwork/android-sdk}} (currently rebranding to OMG Network) - a quite popular cryptocurrency. +A lot of projects uses ktlint as their code formatting tool. For example, OmiseGo \footnote{\url{https://github.com/omgnetwork/android-sdk}} (currently rebranding to OMG Network) - is a quite popular cryptocurrency. + +To summarize: Ktlint is very mature and useful as a framework for creating your own checker\&fixer of Kotlin code and doing AST-analysis. It can be very useful if you need only simple inspections that check (and fix) code-style issues (like indents). + +\begin{figure}[H] + \centering + \includegraphics[scale = 0.6]{pictures/ktlint.png} + \caption{Ktlint Code Frequency} + \label{fig:png_ktlint} +\end{figure} -\par To summarize: Ktlint is very mature and useful as a framework for creating your own checker\&fixer of Kotlin code and doing AST-analysis. It can be very useful if you need only simple inspections that check (and fix) code-style issues (like indents). \subsection{About detekt} -\par Detekt is a static code analysis tool. It operates on an abstract syntax tree (AST) and meta-information provided by Kotlin compiler. On then top of that info, it does a complex analysis of the code. However, this project is more focused on checking the code rather than fixing. Similarly, to ktlint, it has it’s own rules and inspections. Detekt uses wrapped ktlint to redefine rules as it’s formatting rules. +Detekt \footnote{\url{https://github.com/detekt/detekt}} is a static code analysis tool. It operates on an abstract syntax tree (AST) and meta-information provided by Kotlin compiler. On the top of that info, it does a complex analysis of the code. However, this project is more focused on checking the code rather than fixing. Similarly, to ktlint, it has its own rules and inspections. Detekt uses wrapped ktlint to redefine RuleSet of ktlint as it’s formatting rules. -Detekt supports such features as code smell analysis, bugs searching and code-style checking. It has a highly configurable rule sets (can even make suppression of issues from the code). And the number of checkers is quite big: it has more than 100 inspections. Detekt has IntelliJ integration, third-party integrations for Maven, Bazel and Github actions, mechanism for suppression of their warnings with @Suppressannotation and many more. It is being developed since 2016 and today it has 3.2k stars, 411 forks and 1850 closed PRs. It has about 45k lines of code. And it’s codebase is the biggest comparing to other analyzers. -\par Detekt is used in such projects as fountain or Kaspresso. "Fountain is an Android Kotlin library conceived to make your life easier when dealing with paged endpoint services" \footnote{\url{https://github.com/xmartlabs/fountain}} and Kaspresso is a framework for UI testing on Android made by KasperskyLab \footnote{\url{https://github.com/KasperskyLab/Kaspresso}}. +Detekt supports detection of code smells, bugs searching and code-style checking. It has a highly configurable rule sets (can even make suppression of issues from the code). And the number of checkers is large: it has more than 100 inspections. Detekt has IntelliJ integration, third-party integrations for Maven, Bazel and Github actions and a mechanism for suppression of their warnings with @Suppress annotation from the code. It is being developed since 2016 and today it has 3.2k stars, 411 forks and 1850 closed PRs. It has about 45k lines of code. And its codebase is the biggest comparing to other analyzers. +Detekt is used in such projects as fountain \footnote{\url{https://github.com/xmartlabs/fountain}} or Kaspresso \footnote{\url{https://github.com/KasperskyLab/Kaspresso}}. -\par To summarize: Detekt is very useful as a Kotlin static analyser for CI/CD. It tries to find bugs in the code and is focused more on checking of the code. Detekt has 100+ rules that check the code. +To summarize: Detekt is very useful as a Kotlin static analyser for CI/CD. It tries to find bugs in the code and is focused more on checking of the code. Detekt has 100+ rules which check the code. + +\begin{figure}[H] + \centering + \includegraphics[scale = 0.6]{pictures/detekt.png} + \caption{Detekt Code Frequency} + \label{fig:png_detekt} +\end{figure} \subsection{About ktfmt} -\par Ktfmt is a program that formats Kotlin code, based on google-java-format. It's development has started in Facebook in the end of 2019. It can be added to your project through a Maven dependency, Gradle dependency, IntelliJ plugin or you can run it through a command line. Ktfmt is not a configurable application, so to change any rule logic you need to download the project and redefine some constants. Ktfmt has 214 stars, 16 forks, 20 closed PRs and around 7500 lines of code. +Ktfmt formats is a program that formats Kotlin code, based on google-java-format. Its development started in Facebook at the end of 2019. It can be added to client’s project through a Maven dependency, Gradle dependency, IntelliJ plugin or run through a command line. Ktfmt is not a configurable application, so to change any rule logic you need to download the project and redefine some constants. Ktfmt has 214 stars, 16 forks, 20 closed PRs and around 7500 lines of code. + +To summarize: no one knows why Facebook has invested their money in this tool. Nothing new was introduced. If they really needed to have new rules - they could create their own Ruleset for ktlint or detekt. -\par To summarize: no one knows why Facebook has invested their money in this tool. Nothing new was not introduced. +\begin{figure}[H] + \centering + \includegraphics[scale = 0.6]{pictures/ktfmt.png} + \caption{Ktfmt Code Frequency} + \label{fig:png_ktfmt} +\end{figure} \subsection{About diKTat} -Diktat as well as ktlint and detekt is a static code analysis tool. But diktat is not only a tool, but also a coding convention that in details describes all the rules that you should follow when writing a code on Kotlin. It’s development has started in 2020 and at the time of writing this article diKTat has 150 stars and 13 forks. DiKTat operates on AST provided by kotlin compiler. So why diKTat is better? +Diktat is a static code analysis tool as well as ktlint and detect. But diktat is not only a tool, but also a coding convention that describes in details all the rules that you should follow when writing a code on Kotlin. Its development has started in 2020 and at the time of writing this article diKTat has 168 stars and 13 forks. DiKTat operates on AST provided by kotlin compiler. So why diKTat is better? -First of all, it supports much more rules than ktlint. It’s ruleset includes more than 100 rules, that can both check and fix your code. +First of all, it supports much more rules than ktlint. Its ruleset includes more than 100 rules, that can both check and fix your code. -Second, diKTat is configurable. A lot of rules have their own settings, and all of them can be easily understood. For example, you can choose whether you need a copyright, choose a length of line or you can configure your indents. +Secondly, diKTat is configurable. A lot of rules have their own settings, and all of them can be easily understood. For example, you can choose whether you need a copyright, choose a length of line or you can configure your indents. -Third, diKTat is very easy to configure. You don’t need to spend hours only to understand what each rule is doing. Diktat’s ruleset is a .yml file, where each rule is commented out with the description. Also you can suppress error on the particular lines of code using @Suppress annotation in your code. +Third, diKTat is very easy to configure. You don’t need to spend hours only to understand what each rule does. Diktat’s ruleset is a \texttt{.yml} file, where each rule is commented out with the description. Also you can suppress error on the particular lines of code using \texttt{@Suppress} annotation in your code. DiKTat can be used as a CI/CD tool in order to avoid merging errors in the code. Overall it can find code smells and code style issues. Also it can find pretty not obvious bugs by complex AST analysis. Diktat works with maven, gradle and as command-line application powered by ktlint. -\par To summarize: diktat contains a strict coding convention that was not yet introduced by other linters. It works both as a checker and as a fixer. Diktat has much more inspections (100+) and is very configurable (each inspection can be disabled/configured separately), so you can configure it for your particular project. +To summarize: diktat contains a strict coding convention that was not yet introduced by other linters. It works both as a checker and as a fixer. Diktat has much more inspections (100+) and is very configurable (each inspection can be disabled/configured separately), so you can configure it for your particular project. + +\begin{figure}[H] + \centering + \includegraphics[scale = 0.6]{pictures/diktat.png} + \caption{DiKTat Code Frequency} + \label{fig:png_diktat} +\end{figure} \subsection{A few words about Jetbrains} -\par Jetbrains created one of the best IDEs for Java and Kotlin called IntelliJ. This IDE supports a built-in linter. However, it is not a well-configurable tool, you are not able to specify your own coding convention and it is not useful for CI/CD as it is highly coupled with UI. Unfortunately such static analysis is not so effective as it cannot prevent merging of the code with bugs into the repository. As experience shows - many developers simply ignorethose static analysis errors until they are blocked from merging their pull requests. So it is not so suitable for CI/CD, but very good for finding and fixing issues inside your IDE. - -\subsection{Graphics} - -\subsubsection{Detekt Code Frequency} -\hfill\\ - \includegraphics[scale = 0.5]{pictures/detekt.png} -\subsubsection{Ktlint Code Frequency} -\hfill\\ - \includegraphics[scale=0.5]{pictures/ktlint.png} -\subsubsection{Ktfmt Code Frequency} -\hfill\\ - \includegraphics[scale=0.5]{pictures/ktfmt.png} -\subsubsection{DiKTat Code Frequency} -\hfill\\ - \includegraphics[scale=0.5]{pictures/diktat.png} -\subsection{Summary} +Jetbrains invented Kotlin and created one of the best IDEs for Java and Kotlin called IntelliJ. This IDE supports a built-in linter. However, it is not a well-configurable tool, you are not able to specify your own coding convention and it is not useful for CI/CD as it is highly coupled with UI. Unfortunately such static analysis is not so effective as it cannot prevent merging of the code with bugs into the repository. As experience shows - many developers simply ignore those static analysis errors until they are blocked from merging their pull requests. So it is not so suitable for CI/CD, but very good for finding and fixing issues inside your IDE. +\subsection{Summary} +To sum up, four linters, excepting diKTat, were mentioned above and each of them has it's own strengths and weaknesses. Diktat, in its turn, is uniting its strengths and providing new features in code linting and fixing tools. \begin{center} -\begin{tabular}{ |p{3cm}|p{2.5cm}|p{2.5cm}|p{2.5cm}|p{2.5cm}| } +\begin{tabular}{ |p{3cm}|p{2.5cm}|p{2.5cm}|p{2.5cm}|p{2.5cm}| } \hline \multicolumn{5}{|c|}{\textbf{Comparing table}} \\ \hline & diKTat& ktlint &detekt & ktfmt \\ \hline starting year & 2020 & 2016 & 2016 & 2019 \\ -stars & 130 & 3.2k & 3.8k & 214\\ -forks & 12 & 299 & 411 & 16\\ -closed PRs & 226 & 390 & 1850 & 20 \\ -lines of code & 22k & 15k & 45k & 7,5k\\ +stars & 168 & 3.2k & 3.8k & 214\\ +forks & 13 & 299 & 411 & 16\\ +closed PRs & 321 & 390 & 1850 & 20 \\ +lines of code & 32k & 15k & 45k & 7,5k\\ number of rules & $>$100 & $\approx$ 20 & $>$100 & $\approx$ 10 \\ +is configurable & yes & no & yes/no & no \\ +maven/gradle plugin & both & both & gradle only & no \\ +web version & yes & yes & no & no \\ \hline \hline diff --git a/wp/sections/conclusion.tex b/wp/sections/conclusion.tex index dda3a19a79..2632e7c47d 100644 --- a/wp/sections/conclusion.tex +++ b/wp/sections/conclusion.tex @@ -1,2 +1,2 @@ -\par DiKTat is a static code analyzer that finds and fixes code style inconsistencies. DiKTat is configurable, easy-to-use and it implements CI/CD pipelines, which distinguishes it from analogues. We offer many convenient ways to use diktat in projects, so you can use it as Maven/Gradle plugin, CLI tool, Web or github actions. It supports more than 100 rules, where each of them has clear explanation and can be configured by user. -\par When the development of diKTat will finish, we are going to support rules, update frameworks and track latest kotlin language releases to keep diKTat relevant. \ No newline at end of file +\par DiKTat is a static code analyzer that finds and fixes code style inconsistencies. DiKTat is configurable, easy-to-use and it implements CI/CD pipelines, which distinguishes it from analogues. We offer many convenient ways to use diktat in projects, so you can use it as Maven/Gradle plugin, CLI tool, Web or github actions. It supports more than 100 rules, where each of them has clear explanation and can be configured by user. For diktat we have instroduced the coding convention for Kotlin code that now has 6 chapters and will be extended in the future. +\par When the development of diKTat will be finished, we are going to support rules, update frameworks and track latest Kotlin releases to keep diKTat up to date. We are also planning to implement some number of Inspections that can detect real bugs in Kotlin code. \ No newline at end of file diff --git a/wp/sections/definition.tex b/wp/sections/definition.tex index a0b2db8257..8f09353485 100644 --- a/wp/sections/definition.tex +++ b/wp/sections/definition.tex @@ -1,7 +1,6 @@ -Before continue, it is necessary to define some terms so that the reader correctly understands the context of what was written. -The first and basic concept that should be introduced is \textbf{"rule"}. In diKTat, a \textbf{“rule”} is the logic described in a class, which checks a certain paragraph of code style for compliance with the code. - You should also know that \textbf{set} - is a well-defined collection of distinct objects, considered as an object in its own right. - \textbf{Ruleset}, in turn, is a set of such "rules". Suppose $W_i$ is set of checks of i-th rule and $F_i$ is set of fixers of i-th rule. Then $I_i = W_i \cup F_i $ is the set of checks and fixers of the i-th rule. Let R be a ruleset, therefore $R = \sum_{k=1}^n I_k$, where n is number of turned-on rules, $I_k$ is k-th rule. - \textbf{Abstract syntax tree (AST)} is a tree representation of the abstract syntactic structure of source code written in a programming language. Each node of the tree denotes a construct occurring in the source code. - \textbf{CICD} - continuous integration (CI) and continuous delivery (CD) is a methodology that allows application development teams to make changes to code more frequently and reliably. - \textbf{KDoc} - is the language used to document Kotlin code (the equivalent of Java's JavaDoc). \ No newline at end of file +Before we will move one, it is necessary to define some terms for better understanding of context. +The first and basic concept that should be introduced is \textbf{Rule} (marked with $R_i$). Rule in diKTat is the logic described in a special class named with \texttt{"Rule"} suffix, which checks whether code meets a certain paragraph of code-style. The set - is a well-defined collection of distinct objects, considered as an object in its own right. So we can define a \textbf{Ruleset} - a set of such code analysis Rules. We will mark any of such set of Rules with $R$. + +\textbf{Inspection} is the part of any Rule. It is an algorithm that can detect (marked with $W_i$) or fix (marked with $F_i$) invalid code. It is very important to understand that $Rule \neq Inspection (Inspection \subset Rule)$. We will use $I_i$ notation to mark each separate inspection. So it is obvious that: $I_i = W_i \cup F_i$, where $i \in \mathbb {N}$. Using the same logic we can say that $R = \bigcup\limits_{i} R_i$ where $R_i = \bigcup\limits_{j} I_j$. + +\textbf{Abstract syntax tree (AST)} is a tree representation of the abstract syntactic structure of source code written in a programming language (Kotlin in our case). Each node of the tree denotes a construct occurring in the source code. \textbf{CI/CD} - continuous integration (CI) and continuous delivery (CD) is a methodology that allows application development teams to make changes to code more frequently and reliably \cite{ref:cicd}. \textbf{KDoc} - is the language used to document Kotlin code (the equivalent of Java's JavaDoc). \ No newline at end of file diff --git a/wp/sections/diKTat.tex b/wp/sections/diKTat.tex index 47445425e6..3b852c781e 100644 --- a/wp/sections/diKTat.tex +++ b/wp/sections/diKTat.tex @@ -1,5 +1,11 @@ \subsection{What is diKTat?} -DiKTat - is a formal strict code style (\url{https://github.com/cqfn/diKTat}) and a linter with a set of rules that implement this code style for Kotlin language. Basically, it is a collection of Kotlin code style rules implemented as AST visitors on top of KTlint framework (\url{https://github.com/pinterest/ktlint}). Diktat warns and fixes code style errors and code smells based on configuration file. DiKTat is a highly configurable framework, that can be extended further by adding custom rules. It can be run as command line application or with maven or gradle plugins. In this paper, we will explain how DiKTat works, describes advantages and disadvantages and how it differs from other static analyzers. +DiKTat \footnote{\url{https://github.com/cqfn/diKTat}} - is a formal strict code-style for Kotlin language and a linter with a set of rules that implement this code-style. Basically, it is a collection of Kotlin code style rules implemented as AST visitors on top of KTlint framework \footnote{\url{https://github.com/pinterest/ktlint}}. Diktat detects and automatically fixes code style errors and code smells based on the configuration of rules. DiKTat is a highly configurable framework, that can be extended further by adding custom rules. It can be run as command line application or with maven or gradle plugins. In this paper, we will explain how diKTat works, describe its advantages and disadvantages and compare it with other static analyzers for Kotlin. The main idea is to use diktat in your CI/CD pipeline. \subsection{Why diKTat?} -So why did we decide to create diKTat? We looked at similar projects and realized that they have defects and their functionality does not give you a chance to implement modern configurable code style. That’s why we came to a conclusion that we need to create convenient and easy-to-use tool for developers. Why is it easy-to-use? First of all, diKTat has its own highly configurable ruleset. You just need to fill your own options on rules in ruleset or either use default one. Basically, ruleset is an yml file with a description of each rule. Secondly, there are a lot of developers that use different tools for building projects. Most popular are Maven and Gradle. DiKTat supports these ones and it also has cli. Finally, each developer has their own codestyle and sometimes they don’t want static analyzers to trigger on some lines of code. In diKTat you can easily disable a rule. \ No newline at end of file +DiKTat permits formal flexible description or Rules and Inspections expressed by means of yml file. We looked at similar existing projects and realized that their functionality does not give us a chance to implement our own configurable code style. Most of rules which we wanted to implement were missing in other analyzers. Mostly all of those analyzers had hardcoded logic and prohibited configuration. That’s why we decided that we need to create convenient, user friendly and easily configured tool for developers. + +First of all, diKTat has its own highly configurable Ruleset $R_{diktat}$ that contains unique Inspections, missing in other Kotlin static analyzers. You just need to set your own options which fit your project the most. In case you don't want to do this - you can use the default configuration, but some of complex inspections will be disabled. Basically, Ruleset is an \texttt{yml} file with a description of each rule. + +Secondly, DiKTat has its own plugins and can be run via Maven, Gradle and command line. Developer can use build automation system that he prefers. + +Finally, developer can disable with diKTat each inspection from the code using special annotations on the line where he wants to suppress an Inspection. \ No newline at end of file diff --git a/wp/sections/download.tex b/wp/sections/download.tex index be69ad8549..05ac8b516e 100644 --- a/wp/sections/download.tex +++ b/wp/sections/download.tex @@ -1,8 +1,89 @@ \subsection{CLI-application} -\par You can run diKTat as a CLI-application by installing it first. You can find detailed instructions on how to install and run on different OS on github (\url{https://github.com/cqfn/diKTat/blob/master/README.md#run-as-cli-application}). After the run, errors will be found and displayed. Each error consists of a rule name, a description of the rule so that the user can understands the error, the line and column number where the error was found.\\ -\subsection{Plugins} -\par Alternatively, you can add a diktat maven or gradle plugin directly to the project: detailed instructions for maven can be found here - \url{https://github.com/cqfn/diKTat/blob/master/README.md#run-with-maven}and for gralde - \url{https://github.com/cqfn/diKTat/blob/master/README.md#run-with-gradle-plugin}.\\ +You can run diKTat as a CLI-application. To do this you simply need to install ktlint/diktat and to run it via console with a special option \texttt{--disabled\_rules=standard} that we have introduced in ktlint \footnote{\url{https://github.com/pinterest/ktlint/pull/977/files}} : + +\begin{center} +\texttt{\$ ./ktlint -R diktat.jar --disabled\_rules=standard "path/to/project/**/*.kt"} +\end{center} + +After the run, all detected errors will displayed. Each warning contains of a rule name, a description of the rule and line/column where this error appears in the code. It also will contain \texttt{"cannot be auto-corrected"} note if the Inspection does not have autofixer. The format of warning is the following: + +\begin{center} +/path/to/project/file.kt:6:5: [WARNING\_ID\_NAME] free text of the warning (cannot be auto-corrected) +\end{center} + +Please also note, that as diktat is using ktlint framework - the format of the reported warnings can be changed: it can be xml, json and other formats that are supported by ktlint. Please refer to ktlint documentation \footnote{\url{https://github.com/pinterest/ktlint\#creating-a-reporter}} to see the information about custom reporters. + +\subsection{Maven plugin} +Maven plugin was introduced for diktat since the version 0.1.3. The following code snippet from \texttt{pom.xml} shows how to use diktat with Maven plugin: +\begin{lstlisting}[caption={DiKTat with Maven plugin}, label={lst:maven}, language=Kotlin] + + org.cqfn.diktat + diktat-maven-plugin + ${diktat.version} + + + diktat + none + + check + fix + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + diktat-analysis.yml + + ${project.basedir}/src/test/kotlin/excluded + + + + + +\end{lstlisting} + +To run diktat in only-check mode use the command: \texttt{mvn diktat:check@diktat}. To run diktat in autocorrect mode use the command: \texttt{mvn diktat:fix@diktat}. + + +\subsection{Gradle plugin} +This plugin is available since version 0.1.5. The following code snippet shows how to configure Gradle plugin for diktat. + +\begin{lstlisting}[caption={DiKTat with Gradle plugin}, label={lst:gradle1}, language=Kotlin] +plugins { + id("org.cqfn.diktat.diktat-gradle-plugin") version "0.1.7" +} +\end{lstlisting} + +Or use buildscript syntax: + +\begin{lstlisting}[caption={DiKTat with Gradle plugin}, label={lst:gradle1}, language=Kotlin] + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.cqfn.diktat:diktat-gradle-plugin:0.1.7") + } +} +apply(plugin = "org.cqfn.diktat.diktat-gradle-plugin") +\end{lstlisting} + +Then you can configure diktat using diktat extension: + +\begin{lstlisting}[caption={DiKTat extension}, label={lst:gradle2}, language=Kotlin] +diktat { + inputs = files("src/**/*.kt") // file collection that will be checked by diktat + debug = true // turn on debug logging + excludes = files("src/test/kotlin/excluded") // these files will not be checked by diktat +} +\end{lstlisting} + +You can run diktat checks using task \texttt{diktatCheck} and automatically fix errors with tasks \texttt{diktatFix}. + \subsection{Configuratifon file} -As described above, diKTat has a configuration file. Note that you should place the \textsl{diktat-analysis.yml} file containing the diktat configuration in the parent directory of your project when running as a CLI application. Diktat-maven-plugin and diktat-gradle-plugin have a separate option for configuration file path. -\subsection{WEB} -\par Of course, the easiest way to use it without any downloads or installations is the web version of the app. You can try it by following the link \url{https://ktlint-demo.herokuapp.com/demo}. Web app supports both checking and fixing, using either ktlint or diktat ruleset. For diktat you can also upload a custom configuration file. +As described above, diKTat has a configuration file. Note that you should place the \textsl{diktat-analysis.yml} file containing the diktat configuration in the parent directory of your project when running as a CLI application. Diktat-maven-plugin and diktat-gradle-plugin have a separate option to setup the path where the configuration file is stored. + +\subsection{DiKTat-web} +The easiest way to use diktat without any downloads or installations is the web version of the app. You can try it by the following link: \url{https://ktlint-demo.herokuapp.com/demo}. Web app supports both checking and fixing, using either ktlint or diktat ruleset. For diktat you can also upload a custom configuration file. diff --git a/wp/sections/examples.tex b/wp/sections/examples.tex deleted file mode 100644 index 3292ceff48..0000000000 --- a/wp/sections/examples.tex +++ /dev/null @@ -1 +0,0 @@ -Describe how does diKtat work on real projects \ No newline at end of file diff --git a/wp/sections/feature.tex b/wp/sections/feature.tex index e9392d480a..1979cc67b9 100644 --- a/wp/sections/feature.tex +++ b/wp/sections/feature.tex @@ -1,11 +1,11 @@ -\par As described above, diKTat is very configurable and user-friendly. But these are not all of its advantages and features. Below will be presented and described unusual and important killer-features of diKTat. +\par As described above, diKTat is configurable and user-friendly. But these are not all of it's advantages and features. Below we will present and describe unusual and important killer-features of diKTat. \subsection{Configuration file} \par It's worth starting with the configuration file. This is a file in which the user can manually turn rules on and off or configure the rules settings. Below is one of the rules in the configuration file. \\ -\begin{lstlisting}[caption={Part of configuration file.}, -label={lst:example1}, language=Kotlin] +\begin{lstlisting}[ caption={Part of configuration file.}, +label={lst:example1}, language=yaml] - name: DIKTAT_COMMON configuration: # put your package name here - it will be autofixed and checked @@ -24,14 +24,13 @@ \subsection{Configuration file} maxSize: '2000' ignoreFolders: ' ' \end{lstlisting} -Each rule in this file has 3 fields: name - the name of the rule, enabled - whether the rule is enabled or disabled (all rules are enabled by default), configuration - parameters for the rule. With the first two, everything is obvious. The third parameter is less obvious. The configuration is a set of "properties" to configure this rule. For example, for a rule "FILE\underline{ }IS\underline{ }TOO\underline{ }LONG", that checks the number of lines in a Kotlin file, the user can configure the maximum number of lines allowed in the file - by changing the "maxSize" in the configuration, or the user can specify paths to folders that do not need to be checked - by writing the path in "ignoreFolders". \\ +Each Inspection in this file has 3 fields: \texttt{name} - the name of the Inspection, \texttt{enabled} - whether the rule is enabled or disabled (all rules are enabled by default), \texttt{configuration} - parameters for the Inspection. With the first two, everything is obvious. The third parameter is less obvious. The configuration is a set of "properties" to configure this rule. For example, for an Inspection \texttt{FILE\underline{ }IS\underline{ }TOO\underline{ }LONG}, that checks the number of lines in a Kotlin file, the user can configure the maximum number of lines allowed in the file - by changing the "maxSize" in the configuration, or the user can specify paths to folders that do not need to be checked - by writing the path in "ignoreFolders". \\ \subsection{Create ASTNode} -\par -Another feature is a method that allows you to construct an abstract syntax node from text. This algorithm can parse the code even partially, when you do not need to save the hierarchy of the file (with imports/packages/classes). +Another feature is a special mechanism that allows you to construct an abstract syntax tree node from the text. It is extremely useful for creating automatic fixers, because you do not need to think about the AST implementation and you simply need to provide a text block with a code. Everything will be done under the hood by the framework. This algorithm can parse the code even partially, when you do not need to save the hierarchy of the file (with imports/packages/classes). For example it can parse and provide you a sub-tree for these lines of code: -\begin{lstlisting}[caption={Example of creating node.}, label={lst:example1}, language=Kotlin] +\begin{lstlisting}[caption={Example of creating an AST from text of code.}, label={lst:example1}, language=Kotlin] val nodeFromText: ASTNode = KotlinParser().createNode("val age: Int = 21") \end{lstlisting} @@ -66,81 +65,79 @@ \subsection{Create ASTNode} child {node {INTEGER\underline{ }LIRETAL}} }; \end{tikzpicture} +\\ -As you can see in the examples, we pass to the method the text of the source code that we want to transform and the flag, which is set to false by default. The flag should be set to true in order to immediately build a tree with a root node of the FILE type. What's going on inside this method? First of all, the system properties are set (for example: set "idea.io.use.nio2" to true). Further in the method, the text is checked. If the text of the code contains such keywords as import or package, then the method builds a tree with a root node of the FILE type, otherwise it tries with a different root type. In both cases, at the end, if the tree contains an ERROR\underline{ }ELEMENT type of node, it means that an error was made in the code and the method was unable to build the tree and, therefore, throws an exception.\\ -This helps us to implement such complex inspections like the detection of commented code, helps easily fix the code without manually building sub-trees in visitors\\ +As you can see in the example, we pass the text of the source code, that we want to transform, to the method. What's going on inside this method? First of all, special system properties (used by Kotlin parser) are set (for example: set "idea.io.use.nio2" to true). If the text of the code contains high-level keywords like \texttt{import} or \texttt{package}, then the method builds a tree with a root node of the FILE type, otherwise it tries with a different root type. In both cases, at the end, if the tree contains a node with type \texttt{ERROR\underline{ }ELEMENT}, it means that some of the code and the method was unable to build the tree and, therefore, throws an exception.\\ +This helps us to implement such complex inspections like the detection of commented code (and distinguish real comments from commented code blocks), helps easily fix the code without manually building sub-trees in visitors.\\ -\subsection{"SUPPRESS annotation"} +\subsection{Suppress annotation} \par -What if the user wants one of the diKTat rules not to check a piece of code? The \textsl{SUPPRESS} annotation will help with this. This annotation can be supplied to ignore a certain rule. For instance, if run this code: +What if the user wants one of the diKTat Inspections not to check a particular piece of code? The \textsl{SUPPRESS} annotation will help us with it. This annotation can be used to ignore a certain Inspection in a certain code block. For instance, if we run this code: \begin{lstlisting}[caption={Function with incorrect name.}, label={lst:example1}, language=Kotlin] - /** -* This is example -*/ +/** + * This is example + */ package org.cqfn.diktat /** -* Simple class -*/ + * Simple class + */ class User(private val name: String, private val age: Int) { /** - * Function with incorrect name - * - * @return is username longer than age - */ + * Function with incorrect name + * + * @return is username longer than age + */ fun IsInCoRrEcTnAMe() = name.length > age } \end{lstlisting} -There will be warning: +Diktat will raise the warning: $$ \texttt{ \small{ $\big[$FUNCTION\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE$\big]$ function/method name should be in lowerCamelCase}} $$ -But if there is a \textsl{@SUPPRESS} before this method, then there will be no errors at run. -\begin{lstlisting}[caption={Function with incorrect name, but with suppress.}, label={lst:example1}, language=Kotlin] - /** -* This is example -*/ +But if there is a \texttt{@Suppress} before this method, then there will be no warnings during the run: +\begin{lstlisting}[caption={Function with incorrect name, but with suppressed Inspection.}, label={lst:example1}, language=Kotlin] +/** + * This is example + */ package org.cqfn.diktat /** -* Simple class -*/ + * Simple class + */ @Suppress("FUNCTION_NAME_INCORRECT_CASE") class User(private val name: String, private val age: Int) { /** - * Function with incorrect name - * - * @return is username longer than age - */ + * Function with incorrect name + * + * @return is username longer than age + */ fun IsInCoRrEcTnAMe() = name.length > age } \end{lstlisting} -The example shows that the method has SUPPRESS annotation. Therefore, the \\ FUNCTION\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE rule will be ignored on this method and there will be no error. The search method for a given annotation goes up recursively to the root element of type FILE, looking for the annotation. This means that Suppress can be placed not only in front of knowingly incorrect code, but also at the upper levels of the abstract tree. In our example, the annotation is not in front of the method, but in front of the class and still works. Also, you can put several annotations: -\begin{lstlisting}[caption={Function with incorrect name, but with suppress.}, label={lst:example1}, language=Kotlin] +The example shows that the method has a suppress annotation. Therefore, the \texttt{FUNCTION\underline{ }NAME\underline{ }INCORRECT\underline{ }CASE} rule will be ignored on this method and there will be no error. The search method for a given annotation goes up recursively to the root element of type \texttt{FILE}, looking for the annotation. This means that \texttt{@Suppress} can be placed not only in front of knowingly incorrect code, but also at the upper levels of the abstract syntax tree. In our example, the annotation is not in front of the method, but in front of the class and it still works. Also, you can put several annotations: +\begin{lstlisting}[caption={Several suppression annotations}, label={lst:example1}, language=Kotlin] @set:[Suppress("WRONG_DECLARATION_ORDER") Suppress("IDENTIFIER_LENGTH")] \end{lstlisting} \subsection{WEB} -\par -Also worth mentioning is the existence of a web version of diKTat.. This is a very handy tool that can be used quickly, and most importantly, it is very simple. The link can be found in or you can find it in "\nameref{sec:download}" chapter or in ktlint project as reference.\footnote{\url{https://github.com/pinterest/ktlint\#online-demo}} +It worth mentioning that there is a web version of diKTat. This is a handy tool that can be used quickly without any installations, and it is very simple. The link can be found in or you can find it in "\nameref{sec:download}" chapter or in ktlint project as reference.\footnote{\url{https://github.com/pinterest/ktlint\#online-demo}} \begin{figure}[H] \centering \includegraphics[scale=0.3]{pictures/web-example.png} - \caption{Example of web application} + \caption{Diktat-web online demo} \end{figure} -\subsection{Validation rules} -\par +\subsection{Options validation} As it has been mention earlier, diktat has a highly customizable configuration file, but manually editing it is error-prone, for example, name of the rule can be incorrect due to a typo. Diktat will validate configuration file on startup and suggest the closest name based on \textsl{Levenshtein} method. -\subsection{CI-CD} -\par -One of the most important parts of open source project is CI/CD pipeline as it brings considerable benefits to the entire software development process. It improves code quality, customer satisfaction and reduces costs, including time, of making new features. \ No newline at end of file +\subsection{Self checks} +Diktat fully supports self checking of it's own code using both release and snapshot (master) versions. Diktat uses it's self to validate the code inside during it's CI/CD process. diff --git a/wp/sections/introduction.tex b/wp/sections/introduction.tex index af000f4b51..fd132a1ca2 100644 --- a/wp/sections/introduction.tex +++ b/wp/sections/introduction.tex @@ -1,7 +1,7 @@ -It is necessary to conform to a specific style of code during software development, otherwise it will reduce the ability to better understand programmers’ intent and find more functional defects. Analyzers, in turn, have methods for finding and correcting style errors. +It is necessary to follow a specific style of code during software development. Otherwise code will become less readable and the developer will not be able to understand other programmers’ intent. It can lead to functional defects and bugs in the code. Static analyzers, in it's turn, have methods for detecting and auto-correcting style errors and bugs. Modern linters and static analyzers are extremely useful not only for simple code-style analysis but also for bugs detection and automatic code fixing. -Static code analysis is useful not only for optimization and increasing effectiveness but also for automatic error detection. +There are many methods and techniques used by existing analyzers to find bugs (path-sensitive data flow analysis \cite{ref:kremenek}, alias analysis \cite{ref:effective}, type analysis \cite{ref:simple}, symbolic execution \cite{ref:dis}, abstract interpretation \cite{ref:dis}). -There are many methods and techniques used by existing analyzers to find bugs (path-sensitive data flow analysis \cite{ref:kremenek}, alias analysis \cite{ref:effective}, type analysis \cite{ref:simple}, symbolic execution \cite{ref:dis}, abstract interpretation \cite{ref:dis}. +Senior developer can write the same comment again and again in hundreds of code reviews. Static analysis reduces this bureaucracy as it can be thought of as an automated code review process, because it can detect those issues in code automatically. And of course it perfectly reduces the human factor in the review process. There are two main tasks that can be solved by static code analysis: identifying errors (bugs) in programs and recommending code formatting (fixes). This means that the analyzer allows you, for example, to check whether the source code complies with the accepted coding convention and automatically fix found issues. Also, a static analyzer can be used to determine the level of maintainability of a code. It shows how easy is it to read, modify and adapt a given code of software by detecting code-smells and design patterns used in the code. Static analysis tools allow you to identify a large number of errors in the design phase, which significantly reduces the development cost of the entire project. Static analysis covers the entire code - it checks even those code fragments that are difficult to test. It does not depend on the compiler used and the environment in which the compiled program will be executed. -Static analysis can be thought of as an automated code review process. Of the tasks solved by static code analysis programs, two main ones can be distinguished: identifying errors in programs and recommending code formatting. That is, the analyzer allows you to check whether the source code complies with the accepted coding standard. Also, a static analyzer can be used to determine the maintainability of a code, which is how easy it is to analyze, modify and adapt a given software. Static analysis tools allow you to identify a large number of errors in the design phase, which significantly reduces the development cost of the entire project. Static analysis covers the entire code - it checks even those code fragments that are difficult to test. It does not depend on the compiler used and the environment in which the compiled program will be executed. \ No newline at end of file +This white-paper covers the work that was done to create a static analyzer for Kotlin language, called diKTat. It also briefly describes it's implementation and functionality. You can treat this document as a "how-to" instruction for diKTat. \ No newline at end of file diff --git a/wp/sections/kotlin.tex b/wp/sections/kotlin.tex index c79347c6e0..10a2071f3b 100644 --- a/wp/sections/kotlin.tex +++ b/wp/sections/kotlin.tex @@ -1,23 +1,23 @@ -\par Kotlin is a cross-platform, statically typed, general-purpose programming language with type inference. Kotlin is designed to interoperate fully with Java, and the JVM version of Kotlin's standard library depends on the Java Class Library, but type inference allows its syntax to be more concise. Kotlin mainly targets the JVM, but also compiles to JavaScript (e.g. for frontend web applications using React) or native code (via LLVM), e.g. for native iOS apps sharing business logic with Android apps. % wikipedia +This section explains why we are focused exactly on the static analysis for Kotlin language. -\par Kotlin has quickly skyrocketed in popularity. It's used by companies like Google, Square, Pinterest, Pivotal, Netflix and Atlassian. It's the fastest-growing programming language, according to GitHub, growing over 2,5 times in the past year. It was voted one of the five most loved languages, according to Stack Overflow. There are even meetups focused on Kotlin. \footnote{\url{https://www.businessinsider.com/kotlin-programming-language-explained-popularity-2019-5\#:\~:text=Kotlin\%20has\%20quickly\%20skyrocketed\%20in,times\%20in\%20the\%20past\%20year}} -% https://www.businessinsider.com/kotlin-programming-language-explained-popularity-2019-5#:~:text=Kotlin%20has%20quickly%20skyrocketed%20in,times%20in%20the%20past%20year. -\par Kotlin is used in a lot of ways. For example it can be used for backend development using ktor framework (developed by JetBrains), and spring framework also has first-party support for kotlin (Spring is one of the most popular framework on Java for Web development). Kotlin/JS provides the ability to transpile your Kotlin code to JavaScript, as well as providing JS variant of kotlin standard library and interopability with existing JS dependencies, both for Node.js and browser. There are numerous ways that Kotlin/JS can be used. For instance, you can write frontend web applications using Kotlin/JS, -write server-side and serverless applications using Kotlin/JS, create libraries for use with JavaScript and TypeScript. Support for multiplatform programming is one of Kotlin’s key benefits. It reduces time spent writing and maintaining the same code for different platforms while retaining the flexibility and benefits of native programming. +Kotlin is a cross-platform, statically typed, general-purpose programming language with type inference. Kotlin is designed to interoperate fully with Java, and the JVM version of Kotlin's standard library depends on the Java Class Library, but type inference allows its syntax to be more concise. This language can be called "Java on steroids" as it takes best features from different languages and puts it on the top of JVM-world. Kotlin mainly targets the JVM, but also is compiled to JavaScript (e.g. for frontend web applications using React or Thymeleaf) or native code (via LLVM), e.g. for native iOS apps sharing business logic with Android apps. % wikipedia -\par Asynchronous or non-blocking programming is the new reality. Whether we're creating server-side, desktop or mobile applications, it's important that we provide an experience that is not only fluid from the user's perspective, but scalable when needed. -There are many approaches to this problem, and Kotlin takes a very flexible one by providing Coroutine support as a first-party library kotlinx.coroutines with a kotlin compiler plugin and delegating most of the functionality to libraries, much in line with Kotlin's philosophy. -As a bonus, coroutines not only open the doors to asynchronous programming, but also provide a wealth of other possibilities such as concurrency, actors, etc. +Kotlin has quickly skyrocketed in popularity. It's used by Google, Square, Pinterest, Pivotal, Netflix, Atlassian and many other companies. It's the fastest-growing programming language, according to GitHub, growing over 2,5 times in the past year (2019). It was voted one of the five most loved languages, according to Stack Overflow. There are even meetups and conferences focused only on Kotlin\footnote{\url{https://www.businessinsider.com/kotlin-programming-language-explained-popularity-2019-5\#:\~:text=Kotlin\%20has\%20quickly\%20skyrocketed\%20in,times\%20in\%20the\%20past\%20year}}. +% https://www.businessinsider.com/kotlin-programming-language-explained-popularity-2019-5#:~:text=Kotlin%20has%20quickly%20skyrocketed%20in,times%20in%20the%20past%20year -A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines(in a form of kotlinx.coroutines library and kotlin compiler plugin) were added to Kotlin in version 1.3 and are based on established concepts from other languages. +Kotlin is used in a lot of ways. For example, it can be used for backend development using \texttt{ktor} framework (Kotlin framework developed by JetBrains), and \texttt{Spring} framework that also has first-party support for kotlin (\texttt{Spring} is one of the most popular framework on Java for Web development). Kotlin/JS provides the ability to transpile your Kotlin code to JavaScript, as well as providing JS variant of Kotlin standard library and interopability with existing JS dependencies, both for Node.js and browser. There are numerous ways how Kotlin/JS can be used. For instance, Kotlin/JS is used to create frontend web applications, server-side and serverless applications, libraries for use with JavaScript and TypeScript. -On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive. Over 50\% of professional developers who use coroutines have reported seeing increased productivity. Coroutines enable you to write cleaner and more concise app code. +Support for multiplatform programming is one of key benefits. It reduces time for writing and maintaining the same code for different platforms while retaining the flexibility and benefits of native programming. We think that it is the main reason why Kotlin is so popular in the community of mobile developers. -The state of Kotlin in 2020 (according to the latest Kotlin Census and statistical data) +Kotlin supports well asynchronous or non-blocking programming. Whether we're creating server-side, desktop or mobile applications, it's important that we provide an experience that is not only fluid from the user's perspective, but scalable when needed. Kotlin has chosen a very flexible approach one by providing Coroutine support as a first-party library \texttt{kotlinx.coroutines} with a kotlin compiler plugin. A coroutine is a concurrent design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines (in a form of kotlinx.coroutines library and kotlin compiler plugin) were added to Kotlin in version 1.3 and are based on established concepts from other languages. Also, coroutines do not only open the doors to an easy asynchronous programming in Kotlin, but also provide a wealth of other possibilities such as concurrency, actors, etc. + +On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive. Over 50\% of professional developers who use coroutines have reported that productivity had increased. So designers of Kotlin made a correct decision. Coroutines help you to write cleaner and more concise code for your applications. + +The state of Kotlin in the Q3 of 2020 (according to the latest Kotlin Census and statistical data): \begin{itemize} \item 4,7 million users \item 65\% of users use Kotlin in production - \item For 56\% of users, Kotlin is their primary language, which means the main or only + \item Kotlin is primary language for 56\% of users, which means the main or only one they use at work \item 100+ people are on the Kotlin development team at JetBrains \item 350+ independent contributors develop the language and its ecosystem outside @@ -25,10 +25,16 @@ \end{itemize} % https://techcrunch.com/2019/05/07/kotlin-is-now-googles-preferred-language-for-android-app-development/ -Kotlin is widely used among Android developers, including open source OS, like HarmonyOS, that are compatible with Android. In 2019 Google announced that the Kotlin programming language is now its preferred language for Android app developers. In the same year Stack Overflow stated that Kotlin is fourth most loved language in community. Nowadays there are over 60\% of android developers who use Kotlin as their main language. \footnote{\url{https://techcrunch.com/2019/05/07/kotlin-is-now-googles-preferred-language-for-android-app-development/}} -\par Kotlin's popularity can be explained by the rising number of Android users (last year, 124.4m in the USA) and, thus, Android-based devices. 80\% of Kotlin programmers use the language to build Android apps, 31\% for back-end applications, 30\% for SDK/libraries. -Kotlin is also interoperable with Java, which allows developers to use all existing Android libraries in a Kotlin app. Kotlin is in the top of PYPL rating. -\newline -\includegraphics[scale = 0.5]{pictures/kotlinRating.png} -\newline -\par Overall, Kotlin is a modern language that gain it's popularity incredibly fast. It is mostly used by Android developers, but other "branches of programming" are gaining popularity as well, for example spring framework(the most popular Java framework) is supporting Kotlin. It supports both OO (object-oriented) and FP (function-oriented) programming paradigms. Since release 1.4 Kotlin claims to bring major updated every 6 month. \ No newline at end of file +Kotlin is used by developers of open-source operating systems like HarmonyOS and Android. In 2019 Google announced that the Kotlin programming language is now its preferred language for Android app developers. In the same year Stack Overflow stated that Kotlin is fourth most loved language in community. Nowadays there are over 60\% of android developers who use Kotlin as their main language. \footnote{\url{https://techcrunch.com/2019/05/07/kotlin-is-now-googles-preferred-language-for-android-app-development/}} + +Kotlin's popularity can be explained by the rising number of Android users (last year, 124.4m in the USA) and, thus, Android-based devices. 80\% of Kotlin programmers use the language to build Android apps, 31\% for back-end applications, 30\% for SDK/libraries. +Kotlin is also interoperable with Java, which allows developers to use all existing Android libraries in a Kotlin app. Now (2020) Kotlin is in the top-10 of PYPL rating: + +\begin{figure}[H] + \centering + \includegraphics[scale = 0.3]{pictures/kotlinRating.png} + \caption{Top programming languages, 2020} + \label{fig:top_languages} +\end{figure} + +Overall, Kotlin is a modern language that gains its popularity incredibly fast. It is mostly used by Android developers, but other "branches of programming" are gaining popularity as well, for example Spring framework (the most popular Java framework) supports Kotlin. It supports both OO (object-oriented) and FP (function-oriented) programming paradigms. Since the release 1.4 Kotlin claims to bring major updates every 6 month. \ No newline at end of file diff --git a/wp/sections/work.tex b/wp/sections/work.tex index e6c49eb72a..eee8fd8cb4 100644 --- a/wp/sections/work.tex +++ b/wp/sections/work.tex @@ -1,24 +1,26 @@ -Diktat does AST-analysis. This means that for internal representation (IR) it uses Abstract Syntax Tree that was created from the parsed code by the kotlin-compiler. This chapter describes how diktat works +Diktat does AST-analysis, using Abstract Syntax Tree for creation of internal representation (IR) from the parsed code by the kotlin-compiler. This chapter describes how diktat works. \subsection{ktlint} -\par -To quickly and efficiently analyze the program code, you first need to transform it into a convenient data structure. This is exactly what ktlint does - it parses plain text code into an abstract syntax tree. In ktlint, this happens in the \textsl{prepareCodeForLinting}\footnote{\url{https://github.com/pinterest/ktlint/blob/master/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt}} method. This method uses kotlin-compiler-embeddable library to create a root node of type FILE. +To quickly and efficiently analyze the program code, you first need to transform it into a convenient data structure. This is exactly what ktlint does - it parses plain text code into an abstract syntax tree. So we decided not to choose the way of development that was chosen by Facebook in ktfmt and not invent our own framework for parsing the code. We decided to write our own Ruleset on the top of ktlint framework. The good thing is that we were able to inject our set of rules to ktlint via Java's \texttt{ServiceLoader}\footnote{\url{https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html}} + + In ktlint, the transformation of code happens in the \textsl{prepareCodeForLinting}\footnote{\url{https://github.com/pinterest/ktlint/blob/master/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt}} method. This method uses kotlin-compiler-embeddable library to create a root node of type FILE. For example, this simple code: -\begin{lstlisting}[caption={Simple function.}, label={lst:example1}, language=Kotlin] +\begin{lstlisting}[caption={Simple function that will be transformed to AST}, label={lst:example1}, language=Kotlin] fun main() { println("Hello World") } \end{lstlisting} -will be converted into this AST:\\ -\tikzstyle{every node}=[draw=black,thick,anchor=west, scale = 0.5] - +will be converted into the following AST: + +\tikzstyle{every node}=[draw=black,thick,anchor=west, scale = 0.6] + \begin{tikzpicture}[% grow via three points={one child at (0.3,-0.8) and two children at (0.3,-0.8) and (0.3,-1.5)}, - scale=0.5, + scale=0.6, edge from parent path={(\tikzparentnode.south) |- (\tikzchildnode.west)}] - + \node {FILE} child { node {PACKAGE\underline{ }DIRECTIVE}} child { node {IMPORT\underline{ }LIST}} @@ -30,7 +32,7 @@ \subsection{ktlint} child {node {LPAR}} child {node {RPAR}} } - child [missing] {} + child [missing] {} child [missing] {} child {node {WHITE\underline{ }SPACE}} child {node {BLOCK} @@ -80,45 +82,52 @@ \subsection{ktlint} } }; \end{tikzpicture} +\\ -If there are error elements inside the constructed tree, then the corresponding error is displayed. If the code is valid and parsed without errors, for each rule in the ruleset, the \textsl{visit} method is called to which the root node itself and its “children” are sequentially passed. -When you run program, you can pass flags to ktlint - one of them is "-F". This flag means that the rule will not only report an error, but try to fix it. +If there are error elements inside the constructed tree, then the corresponding error is displayed. If the code is valid and parsed without errors, for each rule in the Ruleset, the \textsl{visit} method is called for the root node itself and its “children” are sequentially passed. +When you run program, you can pass flags to ktlint - one of them is \texttt{-F}. This flag means that the rule will not only report an error, but will also try to fix it. -\subsection{diKTat} -\par -Another feature of ktlint is that at startup you can provide a JAR file with additional ruleset(s), which will be discovered by the ServiceLoader and then all nodes will be passed to these rules. This is diKTat! DiKTat is a set of easily configurable rules for static code analysis. The set of all rules is described in the \textsl{DiktatRuleSetProvider}\footnote{\url{https://github.com/cqfn/diKTat/blob/v0.1.3/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt}} class. This class overrides the \textsl{get()} method of the \textsl{RuleSetProvider}\footnote{\url{https://github.com/pinterest/ktlint/blob/master/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/RuleSetProvider.kt}} interface, which returns a set of rules to be "traversed". But before returning this set, the configuration file, in which the user has independently configured all the rules, is read. If there is no configuration file, then a warning will be displayed and the rules will be triggered in according with the default configuration file. -Each rule must implement the \textsl{visit} method of the abstract Rule class, which describes the logic of the rule. +\subsection{DiKTat} -//TODO: add comments -\begin{lstlisting}[caption={Example of rule.}, label={lst:example1}, language=Kotlin] -class SingleLineStatementsRule(private val configRules: List) : Rule("statement") { +Another feature of ktlint is that at it's startup you can provide a JAR file with additional ruleset(s), that will be discovered by the \texttt{ServiceLoader} and then all AST nodes will be passed to these rules. DiKTat uses this approach. - companion object { - private val semicolonToken = TokenSet.create(SEMICOLON) - } +The only modification Diktat makes to the framework is that it adds a mechanism to disable Inspection from the code using annotations or configuration file. The set of all rules is described in the \textsl{DiktatRuleSetProvider}\footnote{\url{https://github.com/cqfn/diKTat/blob/v0.1.3/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt}} class. This class overrides the \textsl{get()} method of the \textsl{RuleSetProvider}\footnote{\url{https://github.com/pinterest/ktlint/blob/master/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/RuleSetProvider.kt}} interface, which returns a set of rules to be "traversed". But before returning this set Diktat is reading the configuration file where the user has independently configured all the Inspections. If there is no configuration file, then a warning will be displayed and Inspections will use the default configuration file. +Each rule must implement the \textsl{visit} method of the abstract Rule class, which describes the logic of the rule. By the way it is a special case of a famous pattern Visitor \cite{ref:gang}. Implementation example of the simple Rule that contains one Inspections can be found below. - private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) +\begin{lstlisting}[caption={Example of the Rule.}, label={lst:example1}, language=Kotlin] +class SingleLineStatementsRule(private val configRules: List) : Rule("statement") { private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType override fun visit(node: ASTNode, autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emit: EmitType) { emitWarn = emit isFixMode = autoCorrect - checkSemicolon(node) - } - - private fun checkSemicolon(node: ASTNode) { - node.getChildren(semicolonToken).forEach { + // all the work is done with ASTNode - this is the type, provided by Kotlin compiler + node.getChildren(TokenSet.create(SEMICOLON)).forEach { if (!it.isFollowedByNewline()) { - MORE_THAN_ONE_STATEMENT_PER_LINE.warnAndFix(configRules, emitWarn, isFixMode, it.extractLineOfText(), - it.startOffset, it) { + // configuration is checked by warning mechanism under the hood + // warnings are mapped to proper paragraph of a code standard + MORE_THAN_ONE_STATEMENT_PER_LINE.warnAndFix( + configRules, + emitWarn, + isFixMode, + it.extractLineOfText(), + it.startOffset, + it + // this lambda provides the logic that will be used to fix the code + ) { if (it.treeParent.elementType == ENUM_ENTRY) { node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) } else { if (!it.isBeginByNewline()) { - val nextNode = it.parent({ parent -> parent.treeNext != null }, strict = false)?.treeNext + val nextNode = it.parent( + { parent -> parent.treeNext != null }, + strict = false + )?.treeNext + node.appendNewlineMergingWhiteSpace(nextNode, it) } node.removeChild(it) @@ -130,28 +139,38 @@ \subsection{diKTat} } \end{lstlisting} -The example above describes the rule in which you cannot write more than two statements on one line. The list of configurations is passed to the parameter of this rule so that the error is displayed only when the rule is enabled (further it will be described how to enable and disable the rule). The class fields and the \textsl{visit} method are described below. The first parameter in method is ASTNode - the node that we got in the parsing in ktlint. Then a check occurs: if the code contains a line in which more than one statement per line and this rule is enabled, then the rule will be executed and, depending on the mode in which the user started ktlint, the rule will either simply report an error or fix it. In our case, when an error is found, the method is called to report and fix the error - \textsl{warnAndFix()}. Warnings that contain similar logic (e.g. regarding formatting of function KDocs) are checked in the same Rule. This way we can parse similar parts of AST only once. Whether the warning is enabled or disabled is checked at the very last moment, inside \textsl{warn()} or \textsl{warnAndFix()} methods. Same is true for suppressions: right before emitting the warning we check whether any of current node's parents has a Suppress annotation. +The example above describes the Rule that checks that there are no statements separated by a semicolon. The list of configurations is passed to the parameter of this rule so that the error is displayed only when the Rule is enabled (further it will be described how to enable or disable the Rule). The class fields and the \textsl{visit()} method are described below. The first parameter in method is of \textsl{ASTNode} type, this type is produced by the Kotlin compiler. It is important to understand that these visitors are called for each and every node of AST that is provided by the compiler. This is not optimal from the perspective of the performance, but makes the code much more readable and isolated. + +Then a check occurs: if the code contains a line in which more than one statement per line and this rule is enabled, then the rule will be executed and, depending on the mode in which the user started ktlint, the rule will either simply report an error or fix it. In our case, when an error is found, the method is called to report and fix the error - \textsl{warnAndFix()}. + +All warnings that contain similar logic (e.g. regarding formatting of function KDocs) are checked in the same Rule. This way we can make small optimisation and check similar parts of AST only once. Whether the Inspection is enabled or disabled is checked inside \textsl{warn()} or \textsl{warnAndFix()} methods. The same works for the suppression of Inspections: right before emitting the warning we check whether any of current node's parents has a Suppress annotation. The diagram below describes it's architecture: + +\begin{figure}[H] + \centering + \includegraphics[scale=0.5]{pictures/class.PNG} + \caption{Diktat class diagram} + \label{fig:top_languages} +\end{figure} + \subsection{Examples of unique inspections} \par -As already described above, diKTat has more rules than existing analogues, therefore, it will find and fix more errors and shortcomings and, thereby, make the code cleaner and better. To better understand how much more detailed the diKTat finds errors, consider a few examples: +As already described above, DiKTat has more rules than existing analogues, therefore, it will find and fix more errors and shortcomings and, thereby, make the code cleaner and better. To better understand how detailed are checks in Diktat,let's mention a few examples: \begin{enumerate} - \item \textbf{Package} -In diKTat, there are about 6 rules only for package (examples). For comparison: detekt has only one rule, where the package name is simply checked by a pattern, in ktlint there is no. - \item \textbf{KDoc} -KDoc is an important part of good code to make it easier to understand and navigate the program. In diKTat there are 15 rules on KDoc, in detekt there are only 7. Therefore, diKTat will make and correct KDoc in more detail and correctly. Examples of rules that have no analogues: examples - \item \textbf{Header} -Like KDoc, header is an essential part of quality code. DiKTat has as many as 6 rules for this, while detekt and ktlint do not. (Examples) + \item \textbf{Package.} +In DiKTat, there are about 6 Inspections that are checking the package naming. For comparison: detekt has only one rule, where the package name is simply checked by a pattern, in ktlint there are zero inspections. + \item \textbf{KDoc.} +KDoc is an important part of good code to make it easier to understand and navigate the program. In DiKTat there are 15 rules on KDoc, in detekt there are only 7. Therefore, DiKTat will make and correct KDoc in more detail and correctly. \end{enumerate} -There are also many unique rules that no analogues have, here are some of them: +There are also many unique Inspections that other analyzers do not have. Here are some of them: \begin{enumerate} - \item \textbf{COMMENTED\underline{ }OUT\underline{ }CODE} – This rule performs checks if there is any commented code. + \item \textbf{COMMENTED\underline{ }OUT\underline{ }CODE} – This Inspection checks if the code contains commented code blocks. \item \textbf{FILE\underline{ }CONTAINS\underline{ }ONLY\underline{ }COMMENTS} – This rule checks file contains not only comments. \item \textbf{LOCAL\underline{ }VARIABLE\underline{ }EARLY\underline{ }DECLARATION} – This rule checks that local variables are declared close to the point where they are first used. - \item \textbf{AVOID\underline{ }NESTED\underline{ }FUNCTIONS} - This rule checks for nested functions and warns and fixes if it finds any. An example of changing the tree when this rule is triggered and diKTat is run with fix mode:\\ + \item \textbf{AVOID\underline{ }NESTED\underline{ }FUNCTIONS} - This rule checks for nested functions and warns and fixes if it finds any. An example of changing the tree when this rule is triggered and DiKTat is run with fix mode:\\\\ \begin{tikzpicture}[% grow via three points={one child at (0.3,-0.8) and two children at (0.3,-0.8) and (0.3,-1.5)}, @@ -188,7 +207,55 @@ \subsection{Examples of unique inspections} }; \draw[-latex,very thick,shorten <=5mm,shorten >=5mm,] ([xshift=5cm,yshift=-3cm]a.north) to ([xshift=-2cm, yshift=-3cm]b.north); +\end{tikzpicture} +\\ + \item \textbf{FLOAT\underline{ }IN\underline{ }ACCURATE\underline{ }CALCULATIONS} - Inspection that checks that floating-point numbers are not used for accurate calculations (see the corresponding Rule from the code style to get more information). + + \item \textbf{PACKAGE\underline{ }NAMING} - This Inspection checks that package name is in a proper format and is separated by dots. This inspection is very demonstrative to show how the work with AST is done. + + This inspection receives different nodes and checks them one by one. First it checks their element type (type of the node in AST). When it's element type equals to \texttt{PACKAGE\_DIRECTIVE} it gets the file name and collects all nodes with \texttt{IDENTIFIER} type as it is shown on the following graph: + +\begin{center} +\begin{tikzpicture}[nodes={draw, circle}, sibling distance=1.5cm, level distance = 1.2cm, minimum size=3.1cm, scale = 1.2, node distance=16cm] +\node(main)[text width = 2cm] { PACKAGE DIRECTIVE} + child { node(a) [below left] { PACKAGE} } + child { node(b)[below] { WHITE SPACE} } + child { node(c)[below right, text width=3cm] { DOT QUALIFIED EXPRESSION} + child { node(d)[below left, text width=2.5cm] { REFERENCE EXPRESSION} + child { node(e)[below, fill = selectColor] { IDENTIFIER} } + } + child { node(f)[below] { DOT} } + child { node(g)[below right, text width=2.5cm] { REFERENCE EXPRESSION} + child { node(h)[below, fill = selectColor] { IDENTIFIER} } + } + }; + \draw[->, line width = 0.5mm] (main) -- (a); + \draw[->, line width = 0.5mm] (main) --(b); + \draw[->, line width = 0.5mm] (main) -- (c); + \draw[->, line width = 0.5mm] (c) -- (d); + \draw[->, line width = 0.5mm] (d) -- (e); + \draw[->, line width = 0.5mm] (c) -- (f); + \draw[->, line width = 0.5mm] (c) -- (g); + \draw[->, line width = 0.5mm] (g) --(h); + \end{tikzpicture} - \item \textbf{FLOAT\underline{ }IN\underline{ }ACCURATE\underline{ }CALCULATIONS} - Rule that checks that floating-point numbers are not used for accurate calculations. -\end{enumerate} +\end{center} + +In order to collect elements with a proper type, Inspection has to do a tree traversal. Tree traversal is done by a special method called \texttt{findAllNodesWithCondition()} (see Listing 3). This function searches for a node with a given condition (in this case it is when node's type equals to \texttt{IDENTIFIER}). As a basis it uses DFS (Depth-first search): it goes recursively in depth of the tree and compares types of AST nodes. When it finds necessary node it returns it and the result from it's parent search as a list, otherwise it returns empty list. At the end the Inspection checks the package name based on identifiers and file name. + + +\begin{lstlisting}[caption={Method for AST traversal}, label={lst:example1}, language=Kotlin] +/** + * This method performs tree traversal and returns all nodes which satisfy the condition + */ +fun ASTNode.findAllNodesWithCondition(condition: (ASTNode) -> Boolean, withSelf: Boolean = true): List { + val result = if (condition(this) && withSelf) mutableListOf(this) else mutableListOf() + return result + this.getChildren(null).flatMap { + it.findAllNodesWithCondition(condition) + } +} +\end{lstlisting} + +\tikzstyle{every node}=[draw=black,thick,anchor=west, scale = 0.5, font = \large] +\end{enumerate} \ No newline at end of file diff --git a/wp/wp.pdf b/wp/wp.pdf new file mode 100644 index 0000000000..1d8eed8b26 Binary files /dev/null and b/wp/wp.pdf differ diff --git a/wp/wp.tex b/wp/wp.tex index 481091cd70..13a7e7492e 100644 --- a/wp/wp.tex +++ b/wp/wp.tex @@ -1,15 +1,14 @@ \documentclass[acmlarge, screen, nonacm]{acmart} \usepackage[utf8]{inputenc} +\usepackage{CJKutf8} \usepackage{graphicx} \usepackage{pgf} \usepackage{multicol} \usepackage{setspace} -\usepackage{charter} \usepackage{listings} \usepackage{hyperref} \usepackage{float} \usepackage{placeins} -\usepackage{graphicx} \graphicspath{ {./images/} } \usepackage{tikz} \usepackage{verbatim} @@ -29,6 +28,32 @@ \definecolor{ForestGreen}{rgb}{0.1334,0.545,0.1334} \definecolor{NavyBlue}{rgb}{0,0.502,0} \definecolor{BurntOrange}{rgb}{0.8,0.3334,0.1334} +\definecolor{selectColor}{rgb}{0.686,1,0.592} + +\newcommand\YAMLcolonstyle{\color{black}\mdseries} +\newcommand\YAMLkeystyle{\color{BurntOrange}\bfseries} +\newcommand\YAMLvaluestyle{\color{black}\mdseries} + +\lstdefinelanguage{yaml} +{ + keywords={true,false,null,y,n}, + keywordstyle=\color{darkgray}\bfseries, + basicstyle=\YAMLkeystyle, + sensitive=false, + comment=[l]{\#}, + morecomment=[s]{/*}{*/}, + commentstyle=\color{ForestGreen}\ttfamily, + stringstyle=\YAMLvaluestyle\ttfamily, + moredelim=[l][\color{orange}]{\&}, + moredelim=[l][\color{magenta}]{*}, + moredelim=**[il][\YAMLcolonstyle{:}\YAMLvaluestyle]{:}, + morestring=[b]', + morestring=[b]", + literate = {---}{{\ProcessThreeDashes}}3 + {>}{{\textcolor{red}\textgreater}}1 + {|}{{\textcolor{red}\textbar}}1 + {\ -\ }{{\mdseries\ -\ }}3, +} \lstdefinestyle{mystyle}{ backgroundcolor=\color{backcolour}, @@ -55,12 +80,12 @@ emph={delegate, filter, first, firstOrNull, forEach, lazy, map, mapNotNull, println, return@}, emphstyle={\color{OrangeRed}}, identifierstyle=\color{black}, - keywords={abstract, actual, as, as?, break, by, class, companion, continue, data, do, dynamic, else, enum, expect, false, final, for, fun, get, if, import, in, interface, internal, is, null, object, override, package, private, public, return, set, super, suspend, this, throw, true, try, typealias, val, var, vararg, when, where, while}, + keywords={abstract, actual, as, as?, break, by, class, companion, continue, data, do, dynamic, else, enum, expect, false, final, for, fun, get, if, import, in, interface, internal, is, object, override, package, private, public, return, set, super, suspend, throw, true, try, typealias, val, var, vararg, when, where, while}, keywordstyle={\color{NavyBlue}\bfseries}, morecomment=[s]{/*}{*/}, morestring=[b]", morestring=[s]{"""*}{*"""}, - ndkeywords={@Deprecated, @JvmField, @JvmName, @JvmOverloads, @JvmStatic, @JvmSynthetic, Array, Byte, Double, Float, Int, Integer, Iterable, Long, Runnable, Short, String}, + ndkeywords={@Deprecated, @JvmField, @JvmName, @JvmOverloads, @JvmStatic, @JvmSynthetic, Array, Byte, Double, Float, this, null, Int, Integer, Iterable, Long, Runnable, Short, String, Boolean}, ndkeywordstyle={\color{BurntOrange}\bfseries}, sensitive=true, stringstyle={\color{ForestGreen}\ttfamily}, @@ -68,6 +93,7 @@ \lstset{style=mystyle} \settopmatter{printacmref=false} +\settopmatter{printfolios=true} \setcopyright{none} \makeatletter \let\@authorsaddresses\@empty @@ -81,9 +107,10 @@ \pagestyle{plain} \begin{document} +\begin{CJK*}{UTF8}{gbsn} \title[]{ - \includegraphics[width = 230pt, height = 200pt]{wp/pictures/logo.png}\\ + \includegraphics[width = 150pt, height = 150]{pictures/logo.png}\\ DiKTat - Kotlin linter } @@ -136,11 +163,11 @@ \section{Comparative analysis} \label{sec:compare} \input{sections/compare} -\section{How does it work} +\section{How does diktat work} \label{sec:work} \input{sections/work} -\section{Killer-Feature} +\section{Killer-Features} \label{sec:feature} \input{sections/feature} @@ -148,10 +175,6 @@ \section{How to use diKTat} \label{sec:download} \input{sections/download} -\section{Examples} -\label{sec:examples} -\input{sections/examples} - \section{Conclusion \& Future Work} \label{sec:conclusion} \input{sections/conclusion} @@ -166,4 +189,5 @@ \section{Appendix} \label{sec:appendix} \input{sections/appendix} +\end{CJK*} \end{document}