diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt index 70bb5a2e47..c391df76c0 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt @@ -1,8 +1,9 @@ package org.cqfn.diktat.common.cli -import kotlinx.serialization.* import org.apache.commons.cli.Option +import kotlinx.serialization.* + /** * This class is used to serialize/deserialize json representation * that is used to store command line arguments diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt index 761f65e2f9..8922045549 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt @@ -1,9 +1,11 @@ package org.cqfn.diktat.common.config.reader +import org.slf4j.LoggerFactory + import java.io.IOException import java.util.Properties + import kotlin.system.exitProcess -import org.slf4j.LoggerFactory /** * Base class for working with properties files. diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/JsonResourceConfigReader.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/JsonResourceConfigReader.kt index 7b15d087c6..3b2cfb772e 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/JsonResourceConfigReader.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/JsonResourceConfigReader.kt @@ -1,10 +1,11 @@ package org.cqfn.diktat.common.config.reader -import java.io.BufferedReader -import java.io.IOException import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.BufferedReader +import java.io.IOException + /** * This class is used to read some resource in any format that you will specify. * Usage: diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt index 358f989687..ed20d51212 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt @@ -4,15 +4,18 @@ package org.cqfn.diktat.common.config.rules +import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader + import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration +import org.slf4j.Logger +import org.slf4j.LoggerFactory + import java.io.BufferedReader import java.io.File + import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString -import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader -import org.slf4j.Logger -import org.slf4j.LoggerFactory const val DIKTAT_COMMON = "DIKTAT_COMMON" @@ -98,7 +101,7 @@ fun List.getCommonConfiguration() = lazy { * * @param configuration map of common configuration */ -class CommonConfiguration(configuration: Map?) { +data class CommonConfiguration(private val configuration: Map?) { /** * List of directory names which will be used to detect test sources */ diff --git a/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt b/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt index b06e235727..72d9a97fb0 100644 --- a/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt +++ b/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt @@ -1,18 +1,20 @@ package org.cqfn.diktat.plugin.maven +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider + import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.RuleExecutionException import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.reporter.plain.PlainReporter -import java.io.File import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugin.MojoExecutionException import org.apache.maven.plugin.MojoFailureException import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider + +import java.io.File /** * Base [Mojo] for checking and fixing code using diktat diff --git a/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatMojo.kt b/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatMojo.kt index e31f890e12..4402230771 100644 --- a/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatMojo.kt +++ b/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatMojo.kt @@ -4,10 +4,12 @@ package org.cqfn.diktat.plugin.maven +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider + import com.pinterest.ktlint.core.KtLint -import java.io.File import org.apache.maven.plugins.annotations.Mojo -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider + +import java.io.File /** * Main [Mojo] that call [DiktatRuleSetProvider]'s rules on [inputs] files 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 80887bb4dd..3bb4e2d3e0 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 @@ -46,6 +46,16 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtTryExpression +/** + * This rule checks that *non-empty* code blocks with braces follow the K&R style (1TBS or OTBS style): + * - The opening brace is on the same same line with the first line of the code block + * - The closing brace is on it's new line + * - The closing brace can be followed by a new line. Only exceptions are: `else`, `finally`, `while` (from do-while statement) or `catch` keywords. + * These keywords should not be split from the closing brace by a newline. + * Exceptions: + * - opening brace of lambda + * - braces around `else`/`catch`/`finally`/`while` (in `do-while` loop) + */ class BlockStructureBraces(private val configRules: List) : Rule("block-structure") { private var isFixMode: Boolean = false private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) 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 25cc20cb2e..d3cf3392db 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 @@ -45,35 +45,40 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy .readResource(diktatConfigFile) ?.onEach(::validate) ?: emptyList() + // Note: the order of rules is important in autocorrect mode. For example, all rules that add new code should be invoked before rules that fix formatting. + // We don't have a way to enforce a specific order, so we should just be careful when adding new rules to this list and, when possible, + // cover new rules in smoke test as well. If a rule needs to be at a specific position in a list, please add comment explaining it (like for NewlinesRule). val rules = listOf( + // comments & documentation ::CommentsRule, ::KdocComments, ::KdocMethods, ::KdocFormatting, + ::CommentsFormatting, + // naming ::FileNaming, ::PackageNaming, - ::StringTemplateFormatRule, - ::FileSize, - ::DataClassesRule, ::IdentifierNaming, - ::LocalVariablesRule, + // code structure ::ClassLikeStructuresOrderRule, + ::WhenMustHaveElseRule, ::BracesInConditionalsAndLoopsRule, ::BlockStructureBraces, ::EmptyBlock, + ::EnumsSeparated, + ::SingleLineStatementsRule, + ::MultipleModifiersSequence, + // other rules + ::StringTemplateFormatRule, + ::DataClassesRule, + ::LocalVariablesRule, ::SmartCastRule, ::PropertyAccessorFields, - ::EnumsSeparated, ::AbstractClassesRule, ::VariableGenericTypeDeclarationRule, - ::SingleLineStatementsRule, - ::CommentsFormatting, - ::ConsecutiveSpacesRule, ::LongNumericalValuesSeparatedRule, ::NestedFunctionBlock, - ::MultipleModifiersSequence, ::AnnotationNewLineRule, - ::HeaderCommentRule, ::SortRule, ::StringConcatenationRule, ::AccurateCalculationsRule, @@ -84,11 +89,14 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy ::LambdaParameterOrder, ::FunctionArgumentsSize, ::BlankLinesRule, + ::FileSize, ::NullableTypeRule, - ::WhiteSpaceRule, - ::WhenMustHaveElseRule, ::ImmutableValNoVarRule, ::AvoidNestedFunctionsRule, + // formatting: moving blocks, adding line breaks, indentations etc. + ::ConsecutiveSpacesRule, + ::WhiteSpaceRule, + ::HeaderCommentRule, ::FileStructureRule, // this rule should be right before indentation because it should operate on already valid code ::NewlinesRule, // newlines need to be inserted right before fixing indentation ::IndentationRule // indentation rule should be the last because it fixes formatting after all the changes done by previous rules diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/WhenMustHaveElseRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/WhenMustHaveElseRule.kt index 35dec76581..ea140db0e7 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/WhenMustHaveElseRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/WhenMustHaveElseRule.kt @@ -51,14 +51,13 @@ class WhenMustHaveElseRule(private val configRules: List) : Rule("n } } - - //FixMe: If a when statement of type enum or sealed contains all values of a enum - there is no need to have "else" branch. - private fun checkEntries(node: ASTNode) { if (!hasElse(node)) { Warnings.WHEN_WITHOUT_ELSE.warnAndFix(configRules, emitWarn, isFixMode, "else was not found", node.startOffset, node) { val whenEntryElse = CompositeElement(WHEN_ENTRY) - node.appendNewlineMergingWhiteSpace(node.lastChildNode.treePrev, node.lastChildNode.treePrev) + if (!node.lastChildNode.isBeginByNewline()) { + node.appendNewlineMergingWhiteSpace(node.lastChildNode.treePrev, node.lastChildNode) + } node.addChild(whenEntryElse, node.lastChildNode) addChildren(whenEntryElse) if(!whenEntryElse.isBeginByNewline()) { @@ -69,7 +68,6 @@ class WhenMustHaveElseRule(private val configRules: List) : Rule("n } private fun isStatement(node: ASTNode) : Boolean { - // Checks if there is return before when if (node.hasParent(RETURN)) { return false @@ -88,7 +86,11 @@ class WhenMustHaveElseRule(private val configRules: List) : Rule("n } } - private fun hasElse(node: ASTNode): Boolean = (node.psi as KtWhenExpression).elseExpression != null + /** + * Check if this `when` has `else` branch. If `else` branch is empty, `(node.psi as KtWhenExpression).elseExpression` returns `null`, + * so we need to manually check if any entry contains `else` keyword. + */ + private fun hasElse(node: ASTNode): Boolean = (node.psi as KtWhenExpression).entries.any { it.isElse } private fun addChildren(node: ASTNode) { val block = PsiBlockStatementImpl() @@ -101,7 +103,6 @@ class WhenMustHaveElseRule(private val configRules: List) : Rule("n addChild(block, null) } - block.apply{ addChild(LeafPsiElement(LBRACE, "{"), null) addChild(PsiWhiteSpaceImpl("\n"),null) @@ -109,6 +110,5 @@ class WhenMustHaveElseRule(private val configRules: List) : Rule("n addChild(PsiWhiteSpaceImpl("\n"),null) addChild(LeafPsiElement(RBRACE, "}"), null) } - } } diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt index 672b9623e3..4f6be67d67 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt @@ -14,4 +14,15 @@ import kotlin.system.exitProcess @ExperimentalStdlibApi public data class Example(val foo: Int, val bar: Double) : SuperExample("lorem ipsum") private class TestException : Exception() +/* this class is unused */ +// private class Test : RuntimeException() + +private fun foo(node: ASTNode) { + when (node.elementType) { + CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node) + else -> { + // this is a generated else block + } + } +} diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt index 53abaac0d2..cd34b64f85 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt @@ -7,4 +7,12 @@ import org.slf4j.LoggerFactory data @ExperimentalStdlibApi public class Example(val foo:Int, val bar:Double):SuperExample("lorem ipsum") -private class Test : Exception() \ No newline at end of file +private class Test : Exception() +/* this class is unused */ +//private class Test : RuntimeException() + +private fun foo (node: ASTNode) { + when (node.elementType) { + CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node) + } +} \ No newline at end of file diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/common/LocalCommandExecutor.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/common/LocalCommandExecutor.kt index c42019ae0f..10bb4949e6 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/common/LocalCommandExecutor.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/common/LocalCommandExecutor.kt @@ -1,8 +1,9 @@ package org.cqfn.diktat.test.framework.common -import java.io.IOException import org.slf4j.LoggerFactory +import java.io.IOException + /** * Class that wraps shell [command] and can execute it */ diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestArgumentsReader.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestArgumentsReader.kt index e5ae0cbe22..714956c94d 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestArgumentsReader.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestArgumentsReader.kt @@ -1,21 +1,24 @@ package org.cqfn.diktat.test.framework.config -import java.io.BufferedReader -import java.io.IOException -import java.util.stream.Collectors -import kotlin.system.exitProcess -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json +import org.cqfn.diktat.common.cli.CliArgument +import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader + import org.apache.commons.cli.CommandLine import org.apache.commons.cli.CommandLineParser import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.HelpFormatter import org.apache.commons.cli.Options import org.apache.commons.cli.ParseException -import org.cqfn.diktat.common.cli.CliArgument -import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader import org.slf4j.LoggerFactory +import java.io.BufferedReader +import java.io.IOException +import java.util.stream.Collectors + +import kotlin.system.exitProcess +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + /** * Class that gives access to properties of a test * diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestConfigReader.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestConfigReader.kt index d4abc3f55f..1ca166c75e 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestConfigReader.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/config/TestConfigReader.kt @@ -1,11 +1,13 @@ package org.cqfn.diktat.test.framework.config +import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader + import java.io.BufferedReader import java.io.IOException import java.util.stream.Collectors + import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader /** * A [JsonResourceConfigReader] to read tests configuration as [TestConfig] diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt index af673ac66a..1b4c3ca772 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt @@ -1,6 +1,8 @@ package org.cqfn.diktat.test.framework.processing import com.github.difflib.DiffUtils +import org.slf4j.LoggerFactory + import java.io.File import java.io.IOException import java.nio.file.Files @@ -8,7 +10,6 @@ import java.nio.file.Paths import java.util.ArrayList import java.util.StringJoiner import java.util.stream.Collectors -import org.slf4j.LoggerFactory /** * A class that is capable of comparing files content diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt index 0de9c19cbd..3991c048c0 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt @@ -1,14 +1,15 @@ package org.cqfn.diktat.test.framework.processing +import org.apache.commons.io.FileUtils +import org.slf4j.Logger +import org.slf4j.LoggerFactory + import java.io.File import java.io.IOException import java.nio.file.Files import java.nio.file.Paths import java.util.ArrayList import java.util.stream.Collectors -import org.apache.commons.io.FileUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory /** * Class that can apply transformation to an input file and then compare with expected result and output difference. diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCompare.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCompare.kt index c50928f123..162fbd869c 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCompare.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCompare.kt @@ -1,14 +1,16 @@ package org.cqfn.diktat.test.framework.processing -import java.io.File -import org.apache.commons.io.FileUtils import org.cqfn.diktat.test.framework.common.ExecutionResult import org.cqfn.diktat.test.framework.common.TestBase import org.cqfn.diktat.test.framework.config.TestConfig import org.cqfn.diktat.test.framework.config.TestFrameworkProperties + +import org.apache.commons.io.FileUtils import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.File + /** * A class that runs tests and compares output with expected result */ diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestProcessingFactory.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestProcessingFactory.kt index 0c67ba5fa1..4b4ed50fd2 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestProcessingFactory.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestProcessingFactory.kt @@ -1,17 +1,20 @@ package org.cqfn.diktat.test.framework.processing -import java.io.File -import java.io.IOException -import java.util.concurrent.atomic.AtomicInteger -import java.util.stream.Stream -import kotlin.system.exitProcess import org.cqfn.diktat.test.framework.common.TestBase import org.cqfn.diktat.test.framework.config.TestArgumentsReader import org.cqfn.diktat.test.framework.config.TestConfig import org.cqfn.diktat.test.framework.config.TestConfig.ExecutionType import org.cqfn.diktat.test.framework.config.TestConfigReader + import org.slf4j.LoggerFactory +import java.io.File +import java.io.IOException +import java.util.concurrent.atomic.AtomicInteger +import java.util.stream.Stream + +import kotlin.system.exitProcess + /** * A class that runs tests based on configuration */