From e9bda99dfd94565ee17810435d49070711807fa1 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Dec 2020 16:17:21 +0300 Subject: [PATCH 1/7] Bugfix for diktat-gradle-plugin, introduced functional tests (#628) ### What's done: * Introduced functional tests for diktat-gradle-plugin * Respect skipTests in diktat-gradle-plugin build * Do not use quotes in patterns * Run gradle daemon in CI builds because gradle is invoked several times from maven * Jacoco test coverage in functionalTest * Updated gradle to 6.7.1 --- .github/workflows/build_and_test.yml | 3 - diktat-gradle-plugin/build.gradle.kts | 45 ++++++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- diktat-gradle-plugin/pom.xml | 21 +++- .../DiktatGradlePluginFunctionalTest.kt | 101 ++++++++++++++++++ .../plugin/gradle/DiktatJavaExecTaskBase.kt | 4 +- .../plugin/gradle/DiktatJavaExecTaskTest.kt | 10 +- 7 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 9dafb60c08..ad8a117897 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -3,9 +3,6 @@ name: Build and test on: pull_request -env: - GRADLE_OPTS: -Dorg.gradle.daemon=false - jobs: test: name: Unit Test diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index 6d75f03456..4c855f2e9a 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -1,9 +1,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem plugins { `java-gradle-plugin` kotlin("jvm") version "1.4.20" jacoco + id("pl.droidsonroids.jacoco.testkit") version "1.0.7" } repositories { @@ -39,7 +41,8 @@ val generateVersionsFile by tasks.registering { doFirst { versionsFile.parentFile.mkdirs() - versionsFile.writeText(""" + versionsFile.writeText( + """ package generated internal const val DIKTAT_VERSION = "$diktatVersion" @@ -75,19 +78,45 @@ java { withSourcesJar() } -// === testing & code coverage, consistent with maven +// === testing & code coverage, jacoco is run independent from maven +val functionalTestTask by tasks.register("functionalTest") +val jacocoMergeTask by tasks.register("jacocoMerge") tasks.withType { useJUnitPlatform() - extensions.configure(JacocoTaskExtension::class) { - setDestinationFile(file("target/jacoco.exec")) - } } +// === integration testing +// fixme: should probably use KotlinSourceSet instead +val functionalTest = sourceSets.create("functionalTest") { + compileClasspath += sourceSets.main.get().output + configurations.testRuntimeClasspath + runtimeClasspath += output + compileClasspath +} +tasks.getByName("functionalTest") { + dependsOn("test") + testClassesDirs = functionalTest.output.classesDirs + classpath = functionalTest.runtimeClasspath + doLast { + if (getCurrentOperatingSystem().isWindows) { + // workaround for https://github.com/koral--/jacoco-gradle-testkit-plugin/issues/9 + logger.lifecycle("Sleeping for 5 sec after functionalTest to avoid error with file locking") + Thread.sleep(5_000) + } + } + finalizedBy(jacocoMergeTask) +} +tasks.check { dependsOn(tasks.jacocoTestReport) } +jacocoTestKit { + applyTo("functionalTestRuntimeOnly", tasks.named("functionalTest")) +} +tasks.getByName("jacocoMerge", JacocoMerge::class) { + dependsOn(functionalTestTask) + executionData(tasks.test, functionalTestTask) +} tasks.jacocoTestReport { - dependsOn(tasks.test) + dependsOn(jacocoMergeTask) + executionData("$buildDir/jacoco/jacocoMerge.exec") reports { // xml report is used by codecov xml.isEnabled = true - xml.destination = file("target/site/jacoco/jacoco.xml") } -} +} \ No newline at end of file diff --git a/diktat-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/diktat-gradle-plugin/gradle/wrapper/gradle-wrapper.properties index be52383ef4..4d9ca16491 100644 --- a/diktat-gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/diktat-gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/diktat-gradle-plugin/pom.xml b/diktat-gradle-plugin/pom.xml index bc804153c6..781a7a1312 100644 --- a/diktat-gradle-plugin/pom.xml +++ b/diktat-gradle-plugin/pom.xml @@ -18,6 +18,9 @@ build build false + false + + @@ -69,7 +72,7 @@ ${gradle.executable} clean - jacocoTestReport + test -Pgroup=${project.groupId} -Pversion=${project.version} -Pdescription=${project.description} @@ -77,7 +80,7 @@ -PjunitVersion=${junit.version} -S - ${skip.gradle.build} + ${skip.gradle.test} exec @@ -96,6 +99,7 @@ -PktlintVersion=${ktlint.version} -PjunitVersion=${junit.version} -S + ${gradle.exclude.check} ${skip.gradle.build} @@ -185,5 +189,18 @@ ${project.basedir}/gradlew.bat + + skip-gradle-tests + + + skipTests + true + + + + -xcheck + true + + \ No newline at end of file diff --git a/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt b/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt new file mode 100644 index 0000000000..595bd9720d --- /dev/null +++ b/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt @@ -0,0 +1,101 @@ +package org.cqfn.diktat.plugin.gradle + +import org.cqfn.diktat.plugin.gradle.DiktatGradlePlugin.Companion.DIKTAT_CHECK_TASK +import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.io.File + +class DiktatGradlePluginFunctionalTest { + private val testProjectDir = TemporaryFolder() + private lateinit var buildFile: File + + @BeforeEach + fun setUp() { + testProjectDir.create() + File("../examples/gradle-kotlin-dsl").copyRecursively(testProjectDir.root) + File(testProjectDir.root, "build.gradle.kts").delete() + buildFile = testProjectDir.newFile("build.gradle.kts").apply { + writeText( + """ + plugins { + id("org.cqfn.diktat.diktat-gradle-plugin") + } + + repositories { + mavenLocal() + mavenCentral() + } + """.trimIndent() + ) + } + } + + @AfterEach + fun tearDown() { + testProjectDir.delete() + } + + @Test + fun `should execute diktatCheck on default values`() { + val result = runDiktat() + + val diktatCheckBuildResult = result.task(":$DIKTAT_CHECK_TASK") + requireNotNull(diktatCheckBuildResult) + Assertions.assertEquals(TaskOutcome.FAILED, diktatCheckBuildResult.outcome) + Assertions.assertTrue( + result.output.contains("[HEADER_MISSING_OR_WRONG_COPYRIGHT]") + ) + } + + @Test + fun `should execute diktatCheck with explicit inputs`() { + buildFile.appendText( + """${System.lineSeparator()} + diktat { + inputs = files("src/**/*.kt") + } + """.trimIndent() + ) + val result = runDiktat() + + val diktatCheckBuildResult = result.task(":$DIKTAT_CHECK_TASK") + requireNotNull(diktatCheckBuildResult) + Assertions.assertEquals(TaskOutcome.FAILED, diktatCheckBuildResult.outcome) + Assertions.assertTrue( + result.output.contains("[HEADER_MISSING_OR_WRONG_COPYRIGHT]") + ) + } + + private fun runDiktat() = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(DIKTAT_CHECK_TASK) + .withPluginClasspath() + .withJaCoCo() + .forwardOutput() + .runCatching { + buildAndFail() + } + .also { + require(it.isSuccess) { "Running gradle returned exception ${it.exceptionOrNull()}" } + } + .getOrNull()!! + + /** + * This is support for jacoco reports in tests run with gradle TestKit + */ + private fun GradleRunner.withJaCoCo() = apply { + javaClass.classLoader + .getResourceAsStream("testkit-gradle.properties") + .also { it ?: println("properties file for testkit is not available, check build configuration") } + ?.use { propertiesFileStream -> + File(projectDir, "gradle.properties").outputStream().use { + propertiesFileStream.copyTo(it) + } + } + } +} diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt index 9eb2cc3afa..b957052117 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt @@ -68,10 +68,10 @@ open class DiktatJavaExecTaskBase @Inject constructor( } .files .forEach { - add("\"${it.path}\"") + add(it.path) } diktatExtension.excludes?.files?.forEach { - add("\"!${it.path}\"") + add(it.path) } } logger.debug("Setting JavaExec args to $args") diff --git a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt index 6c976f80b2..2debadc173 100644 --- a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt +++ b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt @@ -19,7 +19,7 @@ class DiktatJavaExecTaskTest { @Test fun `check command line for various inputs`() { assertCommandLineEquals( - listOf(null, "\"${combinePathParts("src", "**", "*.kt")}\""), + listOf(null, combinePathParts("src", "**", "*.kt")), DiktatExtension().apply { inputs = project.files("src/**/*.kt") } @@ -29,7 +29,7 @@ class DiktatJavaExecTaskTest { @Test fun `check command line in debug mode`() { assertCommandLineEquals( - listOf(null, "--debug", "\"${combinePathParts("src", "**", "*.kt")}\""), + listOf(null, "--debug", combinePathParts("src", "**", "*.kt")), DiktatExtension().apply { inputs = project.files("src/**/*.kt") debug = true @@ -40,8 +40,8 @@ class DiktatJavaExecTaskTest { @Test fun `check command line with excludes`() { assertCommandLineEquals( - listOf(null, "\"${combinePathParts("src", "**", "*.kt")}\"", - "\"!${combinePathParts("src", "main", "kotlin", "generated")}\"" + listOf(null, combinePathParts("src", "**", "*.kt"), + combinePathParts("src", "main", "kotlin", "generated") ), DiktatExtension().apply { inputs = project.files("src/**/*.kt") @@ -70,6 +70,6 @@ class DiktatJavaExecTaskTest { Assertions.assertIterableEquals(expected, task.commandLine) } - private fun combinePathParts(vararg parts: String) = project.projectDir.absolutePath + + private fun combinePathParts(vararg parts: String) = project.rootDir.absolutePath + parts.joinToString(File.separator, prefix = File.separator) } From d8fc1682fe1f6ced116e2a0f7c2bcbd4044f139e Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Dec 2020 17:15:10 +0300 Subject: [PATCH 2/7] Update versions after 0.1.7 release (#632) ### What's done: * Versions are incremented --- .github/workflows/functional_tests.yml | 2 +- .github/workflows/release.yml | 1 - README.md | 8 ++++---- diktat-common/pom.xml | 2 +- diktat-gradle-plugin/build.gradle.kts | 2 +- diktat-gradle-plugin/gradle-plugin-marker/pom.xml | 2 +- diktat-gradle-plugin/pom.xml | 2 +- diktat-maven-plugin/pom.xml | 2 +- diktat-rules/pom.xml | 2 +- diktat-ruleset/pom.xml | 2 +- diktat-test-framework/pom.xml | 2 +- examples/gradle-groovy-dsl/build.gradle | 2 +- examples/gradle-kotlin-dsl/build.gradle.kts | 2 +- examples/maven/pom.xml | 2 +- pom.xml | 4 ++-- 15 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/functional_tests.yml b/.github/workflows/functional_tests.yml index 2fab489f20..1a87ccb3db 100644 --- a/.github/workflows/functional_tests.yml +++ b/.github/workflows/functional_tests.yml @@ -7,7 +7,7 @@ on: branches: [ master ] env: - DIKTAT_VERSION: 0.1.6 + DIKTAT_VERSION: 0.1.7 KTLINT_VERSION: 0.39.0 GRADLE_OPTS: -Dorg.gradle.daemon=false # to speed up gradle run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 798ffdbf7b..c2260a5bbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,6 @@ jobs: mvn -B versions:set -DnextSnapshot=true -DprocessAllModules=true versions:commit mvn versions:set-property -Dproperty=diktat-check.version -DnewVersion=${{ env.RELEASE_VERSION }} sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" README.md || echo "File README.md hasn't been updated!" - sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" .github/workflows/functional_tests.yml || echo "File functional_tests.yml hasn't been updated!" for file in examples/maven/pom.xml examples/gradle-groovy-dsl/build.gradle examples/gradle-kotlin-dsl/build.gradle.kts do sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" $file || echo "File $file hasn't been updated!" diff --git a/README.md b/README.md index 7a287c50e9..0f711c0c03 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ Main features of diktat are the following: # another option is "brew install ktlint" ``` -2. Load diKTat manually: [here](https://github.com/cqfn/diKTat/releases/download/v0.1.6/diktat.jar) +2. Load diKTat manually: [here](https://github.com/cqfn/diKTat/releases/download/v0.1.7/diktat.jar) **OR** use curl: ```bash - $ curl -sSLO https://github.com/cqfn/diKTat/releases/download/v0.1.6/diktat-0.1.6.jar + $ curl -sSLO https://github.com/cqfn/diKTat/releases/download/v0.1.7/diktat-0.1.7.jar ``` 3. Finally, run KTlint (with diKTat injected) to check your `*.kt` files in `dir/your/dir`: @@ -110,7 +110,7 @@ This plugin is available since version 0.1.5. You can see how the plugin is conf Add this plugin to your `build.gradle.kts`: ```kotlin plugins { - id("org.cqfn.diktat.diktat-gradle-plugin") version "0.1.6" + id("org.cqfn.diktat.diktat-gradle-plugin") version "0.1.7" } ``` @@ -121,7 +121,7 @@ buildscript { mavenCentral() } dependencies { - classpath("org.cqfn.diktat:diktat-gradle-plugin:0.1.6") + classpath("org.cqfn.diktat:diktat-gradle-plugin:0.1.7") } } diff --git a/diktat-common/pom.xml b/diktat-common/pom.xml index 97156d5fa9..aa4cc389d3 100644 --- a/diktat-common/pom.xml +++ b/diktat-common/pom.xml @@ -9,7 +9,7 @@ org.cqfn.diktat diktat-parent - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index 4c855f2e9a..b550f9d3af 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -19,7 +19,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.1.6" +val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.1.7" val junitVersion = project.properties.getOrDefault("junitVersion", "5.7.0") as String dependencies { implementation(kotlin("gradle-plugin-api")) diff --git a/diktat-gradle-plugin/gradle-plugin-marker/pom.xml b/diktat-gradle-plugin/gradle-plugin-marker/pom.xml index 01a3ad8fac..f27a81139c 100644 --- a/diktat-gradle-plugin/gradle-plugin-marker/pom.xml +++ b/diktat-gradle-plugin/gradle-plugin-marker/pom.xml @@ -4,7 +4,7 @@ diktat-gradle-plugin org.cqfn.diktat - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT 4.0.0 diff --git a/diktat-gradle-plugin/pom.xml b/diktat-gradle-plugin/pom.xml index 781a7a1312..c112c275ff 100644 --- a/diktat-gradle-plugin/pom.xml +++ b/diktat-gradle-plugin/pom.xml @@ -5,7 +5,7 @@ diktat-parent org.cqfn.diktat - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT 4.0.0 diff --git a/diktat-maven-plugin/pom.xml b/diktat-maven-plugin/pom.xml index 1d0e1c4ed2..15b92fa19c 100644 --- a/diktat-maven-plugin/pom.xml +++ b/diktat-maven-plugin/pom.xml @@ -5,7 +5,7 @@ diktat-parent org.cqfn.diktat - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT 4.0.0 diff --git a/diktat-rules/pom.xml b/diktat-rules/pom.xml index 63d55dc7c0..4ef41b064d 100644 --- a/diktat-rules/pom.xml +++ b/diktat-rules/pom.xml @@ -9,7 +9,7 @@ org.cqfn.diktat diktat-parent - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT diff --git a/diktat-ruleset/pom.xml b/diktat-ruleset/pom.xml index 97b858650d..aa80a79528 100644 --- a/diktat-ruleset/pom.xml +++ b/diktat-ruleset/pom.xml @@ -8,7 +8,7 @@ org.cqfn.diktat diktat-parent - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT diff --git a/diktat-test-framework/pom.xml b/diktat-test-framework/pom.xml index ba2409235d..3dff8b682a 100644 --- a/diktat-test-framework/pom.xml +++ b/diktat-test-framework/pom.xml @@ -9,7 +9,7 @@ org.cqfn.diktat diktat-parent - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT diff --git a/examples/gradle-groovy-dsl/build.gradle b/examples/gradle-groovy-dsl/build.gradle index 853bc6778d..0a897a3272 100644 --- a/examples/gradle-groovy-dsl/build.gradle +++ b/examples/gradle-groovy-dsl/build.gradle @@ -1,5 +1,5 @@ plugins { - id "org.cqfn.diktat.diktat-gradle-plugin" version "0.1.6" + id "org.cqfn.diktat.diktat-gradle-plugin" version "0.1.7" } repositories { diff --git a/examples/gradle-kotlin-dsl/build.gradle.kts b/examples/gradle-kotlin-dsl/build.gradle.kts index 8f9c4a5d45..8eb7b27e2e 100644 --- a/examples/gradle-kotlin-dsl/build.gradle.kts +++ b/examples/gradle-kotlin-dsl/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.cqfn.diktat.diktat-gradle-plugin") version "0.1.6" + id("org.cqfn.diktat.diktat-gradle-plugin") version "0.1.7" } repositories { diff --git a/examples/maven/pom.xml b/examples/maven/pom.xml index f6c091a673..b703cfe7c7 100644 --- a/examples/maven/pom.xml +++ b/examples/maven/pom.xml @@ -12,7 +12,7 @@ org.cqfn.diktat diktat-maven-plugin - 0.1.6 + 0.1.7 diktat-analysis.yml diff --git a/pom.xml b/pom.xml index e78353c84b..e08a8758cc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.cqfn.diktat diktat-parent - 0.1.7-SNAPSHOT + 0.1.8-SNAPSHOT pom diktat @@ -50,7 +50,7 @@ 30.0-jre 1.7.30 1.4 - 0.1.6 + 0.1.7 1.7.1 1.14.2 1.4.20 From 791413b765187f5a42dd94e98854bd031fe56810 Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Fri, 11 Dec 2020 12:48:49 +0300 Subject: [PATCH 3/7] Bugfix. FP EMPTY_BLOCK_STRUCTURE_ERROR on SAM overrides * bugfix/empty-block-on-sam ### What's done: * Fixed bugs --- .../cqfn/diktat/ruleset/rules/EmptyBlock.kt | 21 +++++++- .../cqfn/diktat/ruleset/utils/KotlinParser.kt | 1 - .../ruleset/chapter3/EmptyBlockWarnTest.kt | 54 +++++++++++++++++-- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/EmptyBlock.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/EmptyBlock.kt index f60e14df43..ae92bb5a6c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/EmptyBlock.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/EmptyBlock.kt @@ -6,10 +6,17 @@ import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.EmitType import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_BLOCK_STRUCTURE_ERROR import org.cqfn.diktat.ruleset.utils.findLBrace +import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType +import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType +import org.cqfn.diktat.ruleset.utils.hasParent import org.cqfn.diktat.ruleset.utils.isBlockEmpty import org.cqfn.diktat.ruleset.utils.isOverridden +import org.cqfn.diktat.ruleset.utils.isPascalCase import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL +import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -42,7 +49,7 @@ class EmptyBlock(private val configRules: List) : Rule("empty-block @Suppress("UnsafeCallOnNullableType") private fun checkEmptyBlock(node: ASTNode, configuration: EmptyBlockStyleConfiguration) { - if (node.treeParent.isOverridden()) { + if (node.treeParent.isOverridden() || isAnonymousSamClass(node)) { return } if (node.isBlockEmpty()) { @@ -70,6 +77,18 @@ class EmptyBlock(private val configRules: List) : Rule("empty-block } } + @Suppress("UnsafeCallOnNullableType", "WRONG_WHITESPACE") + private fun isAnonymousSamClass(node: ASTNode) : Boolean = + if (node.elementType == FUNCTION_LITERAL && node.hasParent(CALL_EXPRESSION)) { + // We are checking identifier because it is not class in AST + // , SAM conversions are indistinguishable from lambdas. + // So we just verify that identifier is in PascalCase + val valueArgument = node.findParentNodeWithSpecificType(CALL_EXPRESSION)!! + valueArgument.findLeafWithSpecificType(IDENTIFIER)?.text?.isPascalCase() ?: false + } else { + false + } + /** * [RuleConfiguration] for empty blocks formatting */ diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt index 38aff0df5e..8243f21656 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt @@ -29,7 +29,6 @@ import sun.reflect.ReflectionFactory * A class that wraps kotlin compiler's code parser and converts source code into AST */ class KotlinParser { - @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") private val project: Project by lazy { val compilerConfiguration = CompilerConfiguration() compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) // mute the output logging to process it themselves diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt index f800de9853..283dcf0f9a 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt @@ -133,15 +133,61 @@ class EmptyBlockWarnTest : LintTestBase(::EmptyBlock) { } @Test + @Tag(WarningNames.EMPTY_BLOCK_STRUCTURE_ERROR) fun `check empty lambda with config`() { lintMethod( """ - |fun foo() { - | val y = listOf().map {} - |} - """.trimMargin(), + |fun foo() { + | val y = listOf().map {} + |} + """.trimMargin(), LintError(2, 30, ruleId, "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} different style for empty block", true), rulesConfigList = rulesConfigListEmptyBlockExist ) } + + @Test + fun `should not trigger on anonymous SAM classes #1`() { + lintMethod( + """ + |fun foo() { + | val proj = some.create( + | Disposable {}, + | config + | ).project + |} + """.trimMargin(), + rulesConfigList = rulesConfigListEmptyBlockExist + ) + } + + @Test + fun `should not trigger on anonymous SAM classes #2`() { + lintMethod( + """ + |fun foo() { + | val some = Disposable {} + | val proj = some.create( + | some, + | config + | ).project + |} + """.trimMargin(), + rulesConfigList = rulesConfigListEmptyBlockExist + ) + } + + @Test + @Tag(WarningNames.EMPTY_BLOCK_STRUCTURE_ERROR) + fun `should trigger on implementing anonymous SAM classes`() { + lintMethod( + """ + |interface A + | + |val some = object : A{} + """.trimMargin(), + LintError(3, 22, ruleId, "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} different style for empty block", true), + rulesConfigList = rulesConfigListEmptyBlockExist + ) + } } From 6ea31f79baf3f139ca7e9739b5ec58d5c9cb0fec Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Fri, 11 Dec 2020 15:57:14 +0300 Subject: [PATCH 4/7] Feature. Recommendation 6.4.2: Objects should be used for Stateless Interfaces * feature/recommendation-6.4.2(#449) ### What's done: * Added rule logic * Added warn tests * Added fix tests --- diktat-analysis.yml | 3 + diktat-gradle-plugin/gradlew.bat | 0 .../src/main/kotlin/generated/WarningNames.kt | 2 + .../cqfn/diktat/ruleset/constants/Warnings.kt | 1 + .../ruleset/rules/DiktatRuleSetProvider.kt | 4 +- .../rules/classes/StatelessClassesRule.kt | 98 +++++++++++++++++++ .../main/resources/diktat-analysis-huawei.yml | 3 + .../src/main/resources/diktat-analysis.yml | 3 + .../chapter6/StatelessClassesRuleFixTest.kt | 16 +++ .../chapter6/StatelessClassesRuleWarnTest.kt | 83 ++++++++++++++++ .../StatelessClassExpected.kt | 16 +++ .../stateless_classes/StatelessClassTest.kt | 16 +++ info/available-rules.md | 3 +- 13 files changed, 246 insertions(+), 2 deletions(-) mode change 100644 => 100755 diktat-gradle-plugin/gradlew.bat create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/StatelessClassesRule.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleFixTest.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleWarnTest.kt create mode 100644 diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassExpected.kt create mode 100644 diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassTest.kt diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 2010f51806..38a03d1f58 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -311,4 +311,7 @@ enabled: true # Checks if there is class/object that can be replace with extension function - name: AVOID_USING_UTILITY_CLASS + enabled: true +# If there is stateless class it is preferred to use object +- name: OBJECT_IS_PREFERRED enabled: true \ No newline at end of file diff --git a/diktat-gradle-plugin/gradlew.bat b/diktat-gradle-plugin/gradlew.bat old mode 100644 new mode 100755 diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 52735c2a16..10842cf258 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -227,4 +227,6 @@ public object WarningNames { public const val NO_CORRESPONDING_PROPERTY: String = "NO_CORRESPONDING_PROPERTY" public const val AVOID_USING_UTILITY_CLASS: String = "AVOID_USING_UTILITY_CLASS" + + public const val OBJECT_IS_PREFERRED: String = "OBJECT_IS_PREFERRED" } 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 e0c7f06f0e..156d662dae 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 @@ -146,6 +146,7 @@ enum class Warnings( EMPTY_PRIMARY_CONSTRUCTOR(true, "6.1.3", "avoid empty primary constructor"), 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"), ; /** 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 b9c8c4a57b..bdc99d4d77 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 @@ -10,6 +10,7 @@ import org.cqfn.diktat.ruleset.rules.classes.CompactInitialization import org.cqfn.diktat.ruleset.rules.classes.DataClassesRule import org.cqfn.diktat.ruleset.rules.classes.SingleConstructorRule import org.cqfn.diktat.ruleset.rules.classes.SingleInitRule +import org.cqfn.diktat.ruleset.rules.classes.StatelessClassesRule import org.cqfn.diktat.ruleset.rules.comments.CommentsRule import org.cqfn.diktat.ruleset.rules.comments.HeaderCommentRule import org.cqfn.diktat.ruleset.rules.files.BlankLinesRule @@ -52,7 +53,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS val diktatExecutionPath = File(diktatConfigFile) if (!diktatExecutionPath.exists()) { // for some aggregators of static analyzers we need to provide configuration for cli - // in this case diktat would take the configuration from the direcory where jar file is stored + // in this case diktat would take the configuration from the directory where jar file is stored val ruleSetProviderPath = DiktatRuleSetProvider::class .java @@ -112,6 +113,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::CustomGetterSetterRule, ::CompactInitialization, // other rules + ::StatelessClassesRule, ::ImplicitBackingPropertyRule, ::StringTemplateFormatRule, ::DataClassesRule, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/StatelessClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/StatelessClassesRule.kt new file mode 100644 index 0000000000..8a3da3bf1d --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/StatelessClassesRule.kt @@ -0,0 +1,98 @@ +package org.cqfn.diktat.ruleset.rules.classes + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.hasChildOfType + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.CLASS +import com.pinterest.ktlint.core.ast.ElementType.CLASS_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.FILE +import com.pinterest.ktlint.core.ast.ElementType.FUN +import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER +import com.pinterest.ktlint.core.ast.ElementType.INTERFACE_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.OBJECT_DECLARATION +import com.pinterest.ktlint.core.ast.ElementType.OBJECT_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_ENTRY +import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST +import com.pinterest.ktlint.core.ast.children +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.KtClass + +/** + * This rule checks if class is stateless and if so changes it to object. + */ +class StatelessClassesRule(private val configRule: List) : Rule("stateless-class") { + private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType + + override fun visit(node: ASTNode, + autoCorrect: Boolean, + emit: EmitType) { + emitWarn = emit + isFixMode = autoCorrect + + // Fixme: We should find interfaces in all project and then check them + if (node.elementType == FILE) { + val interfacesNodes = node + .findAllNodesWithSpecificType(CLASS) + .filter { it.hasChildOfType(INTERFACE_KEYWORD) } + node + .findAllNodesWithSpecificType(CLASS) + .filterNot { it.hasChildOfType(INTERFACE_KEYWORD) } + .forEach { handleClass(it, interfacesNodes) } + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun handleClass(node: ASTNode, interfaces: List) { + if (isClassExtendsValidInterface(node, interfaces) && isStatelessClass(node)) { + Warnings.OBJECT_IS_PREFERRED.warnAndFix(configRule, emitWarn, isFixMode, + "class ${(node.psi as KtClass).name!!}", node.startOffset, node) { + val newObjectNode = CompositeElement(OBJECT_DECLARATION) + node.treeParent.addChild(newObjectNode, node) + node.children().forEach { + newObjectNode.addChild(it.copyElement(), null) + } + newObjectNode.addChild(LeafPsiElement(OBJECT_KEYWORD, "object"), + newObjectNode.getFirstChildWithType(CLASS_KEYWORD)) + newObjectNode.removeChild(newObjectNode.getFirstChildWithType(CLASS_KEYWORD)!!) + node.treeParent.removeChild(node) + } + } + } + + private fun isStatelessClass(node: ASTNode): Boolean { + val properties = (node.psi as KtClass).getProperties() + val functions = node.findAllNodesWithSpecificType(FUN) + return properties.isNullOrEmpty() && + functions.isNotEmpty() && + !(node.psi as KtClass).hasExplicitPrimaryConstructor() + } + + private fun isClassExtendsValidInterface(node: ASTNode, interfaces: List): Boolean = + node.findChildByType(SUPER_TYPE_LIST) + ?.getAllChildrenWithType(SUPER_TYPE_ENTRY) + ?.isNotEmpty() + ?.and(isClassInheritsStatelessInterface(node, interfaces)) + ?: false + + @Suppress("UnsafeCallOnNullableType") + private fun isClassInheritsStatelessInterface(node: ASTNode, interfaces: List): Boolean { + val classInterfaces = node + .findChildByType(SUPER_TYPE_LIST) + ?.getAllChildrenWithType(SUPER_TYPE_ENTRY) + + val foundInterfaces = interfaces.filter { inter -> + classInterfaces!!.any { it.text == inter.getFirstChildWithType(IDENTIFIER)!!.text } + } + + return foundInterfaces.any { (it.psi as KtClass).getProperties().isEmpty() } + } +} diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 99342d1c1e..3d8614fc42 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -309,4 +309,7 @@ enabled: true # Checks if there is class/object that can be replace with extension function - name: AVOID_USING_UTILITY_CLASS + enabled: true +# If there is stateless class it is preferred to use object +- name: OBJECT_IS_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 05983b1d53..a043d1d4d5 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -311,4 +311,7 @@ enabled: true # Checks if there is class/object that can be replace with extension function - name: AVOID_USING_UTILITY_CLASS + enabled: true +# If there is stateless class it is preferred to use object +- name: OBJECT_IS_PREFERRED enabled: true \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleFixTest.kt new file mode 100644 index 0000000000..f7938845b6 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleFixTest.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import org.cqfn.diktat.ruleset.rules.classes.StatelessClassesRule +import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames.OBJECT_IS_PREFERRED +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class StatelessClassesRuleFixTest : FixTestBase("test/chapter6/stateless_classes", ::StatelessClassesRule) { + @Test + @Tag(OBJECT_IS_PREFERRED) + fun `fix class to object keyword`() { + fixAndCompare("StatelessClassExpected.kt", "StatelessClassTest.kt") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleWarnTest.kt new file mode 100644 index 0000000000..3717958826 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleWarnTest.kt @@ -0,0 +1,83 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.classes.StatelessClassesRule +import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames.OBJECT_IS_PREFERRED +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class StatelessClassesRuleWarnTest : LintTestBase(::StatelessClassesRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:stateless-class" + + @Test + @Tag(OBJECT_IS_PREFERRED) + fun `should not trigger on class not extending any interface`() { + lintMethod( + """ + |class Some : I() { + | override fun some() + |} + """.trimMargin() + ) + } + + @Test + @Tag(OBJECT_IS_PREFERRED) + fun `should trigger on class extending interface`() { + lintMethod( + """ + |class Some : I { + | override fun some() + |} + | + |interface I { + | fun some() + |} + """.trimMargin(), + LintError(1, 1, ruleId, "${Warnings.OBJECT_IS_PREFERRED.warnText()} class Some", true) + ) + } + + @Test + @Tag(OBJECT_IS_PREFERRED) + fun `should not trigger on class with constructor`() { + lintMethod( + """ + |class Some(b: Int) : I { + | + | override fun some() + |} + """.trimMargin() + ) + } + + @Test + @Tag(OBJECT_IS_PREFERRED) + fun `should not trigger on class with no interface in this file`() { + lintMethod( + """ + |class Some : I { + | + | override fun some() + |} + """.trimMargin() + ) + } + + @Test + @Tag(OBJECT_IS_PREFERRED) + fun `should not trigger on class with state`() { + lintMethod( + """ + |class Some : I { + | val a = 5 + | override fun some() + |} + """.trimMargin() + ) + } +} diff --git a/diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassExpected.kt b/diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassExpected.kt new file mode 100644 index 0000000000..ed065cfdfd --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassExpected.kt @@ -0,0 +1,16 @@ +package test.chapter6.stateless_classes + +interface I { + fun foo() +} + +object O: I { + override fun foo() {} +} + +/** + * Some KDOC + */ +object A: I { + override fun foo() {} +} diff --git a/diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassTest.kt b/diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassTest.kt new file mode 100644 index 0000000000..dd4fc6614c --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassTest.kt @@ -0,0 +1,16 @@ +package test.chapter6.stateless_classes + +interface I { + fun foo() +} + +class O: I { + override fun foo() {} +} + +/** + * Some KDOC + */ +class A: I { + override fun foo() {} +} diff --git a/info/available-rules.md b/info/available-rules.md index 481807504e..55d21e7082 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -106,4 +106,5 @@ | 6 | 6.1.8 | CUSTOM_GETTERS_SETTERS | Check: Inspection that checks that no custom getters and setters are used for properties | no | - | - | | 6 | 6.1.11 | COMPACT_OBJECT_INITIALIZATION | Checks if class instantiation can be wrapped in `apply` for better readability | | | | | 6 | 6.2.2 | EXTENSION_FUNCTION_SAME_SIGNATURE | Checks if extension function has the same signature as another extension function and their classes are related | no | - | + | -| 6 | 6.4.1 | AVOID_USING_UTILITY_CLASS | Checks if there is class/object that can be replace with extension function | no | - | - | \ No newline at end of file +| 6 | 6.4.1 | AVOID_USING_UTILITY_CLASS | Checks if there is class/object that can be replace with extension function | no | - | - | +| 6 | 6.4.2 | OBJECT_IS_PREFERRED | Checks: if class is stateless it is preferred to use `object` | yes | - | + | \ No newline at end of file From d690c0f2ac4894d34691f092fc49d1e9b13b073d Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Mon, 14 Dec 2020 10:24:06 +0300 Subject: [PATCH 5/7] Fixes for FileStructureRule (#636) ### What's done: * Fixed logic * Added tests --- .../rules/comments/HeaderCommentRule.kt | 2 +- .../ruleset/rules/files/FileStructureRule.kt | 48 +++++++---- .../cqfn/diktat/ruleset/utils/AstConstants.kt | 1 + .../cqfn/diktat/ruleset/utils/AstNodeUtils.kt | 2 +- .../chapter3/FileStructureRuleFixTest.kt | 6 ++ .../ruleset/chapter3/FileStructureRuleTest.kt | 80 +++++++++++++++++++ .../file_structure/OtherCommentsExpected.kt | 11 +++ .../file_structure/OtherCommentsTest.kt | 11 +++ 8 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsExpected.kt create mode 100644 diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsTest.kt 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 9f9a96a39f..5fabfe31e4 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 @@ -10,6 +10,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_MISSING_OR_WRONG_COPYRI 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.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 @@ -40,7 +41,6 @@ import java.time.LocalDate */ @Suppress("ForbiddenComment") class HeaderCommentRule(private val configRules: List) : Rule("header-comment") { - private val copyrightWords = setOf("copyright", "版权") private var isFixMode: Boolean = false private lateinit var emitWarn: EmitType diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt index a1c092e995..888eb7a7b0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/FileStructureRule.kt @@ -12,6 +12,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.FILE_UNORDERED_IMPORTS import org.cqfn.diktat.ruleset.constants.Warnings.FILE_WILDCARD_IMPORTS import org.cqfn.diktat.ruleset.rules.PackageNaming.Companion.PACKAGE_SEPARATOR import org.cqfn.diktat.ruleset.utils.StandardPlatforms +import org.cqfn.diktat.ruleset.utils.copyrightWords import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder import org.cqfn.diktat.ruleset.utils.moveChildBefore @@ -37,6 +38,7 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.psiUtil.siblings /** * Visitor for checking internal file structure. @@ -100,17 +102,17 @@ class FileStructureRule(private val configRules: List) : Rule("file return hasCode } - @Suppress("TOO_LONG_FUNCTION") + @Suppress("ComplexMethod", "TOO_LONG_FUNCTION") private fun checkCodeBlocksOrderAndEmptyLines(node: ASTNode) { - // PACKAGE_DIRECTIVE node is always present in regular kt files and might be absent in kts. + // From KtFile.kt: 'scripts have no package directive, all other files must have package directives'. // Kotlin compiler itself enforces it's position in the file if it is present. // If package directive is missing in .kt file (default package), the node is still present in the AST. - // fixme: find and handle cases when this node is not present (.kts files?) val packageDirectiveNode = (node.psi as KtFile) .packageDirective ?.takeUnless { it.isRoot } ?.node - // fixme: find cases when node.psi.importLists.size > 1, handle cases when it's not present (null) + // There is a private property node.psi.importLists, but it's size can't be > 1 in valid kotlin code. It exists to help in situations + // when, e.g. merge conflict marker breaks the imports list. We shouldn't handle this situation here. val importsList = (node.psi as KtFile) .importList ?.takeIf { it.imports.isNotEmpty() } @@ -123,19 +125,34 @@ class FileStructureRule(private val configRules: List) : Rule("file // taking nodes with actual code !it.isWhiteSpace() && !it.isPartOfComment() && // but not the ones we are going to move - it.elementType != FILE_ANNOTATION_LIST && it.elementType != IMPORT_LIST && - // if we are here, then package is default and we don't need to select the empty PACKAGE_DIRECTIVE node + it.elementType != FILE_ANNOTATION_LIST && + // if we are here, then IMPORT_LIST either is not present in the AST, or is empty. Either way, we don't need to select it. + it.elementType != IMPORT_LIST && + // if we are here, then package is default and we don't need to select the empty PACKAGE_DIRECTIVE node. it.elementType != PACKAGE_DIRECTIVE } ?: return // at this point it means the file contains only comments - // fixme: handle other elements that could be present before package (other comments) + // We consider the first block comment of the file to be the one that possibly contains copyright information. var copyrightComment = firstCodeNode.prevSibling { it.elementType == BLOCK_COMMENT } + ?.takeIf { blockCommentNode -> + copyrightWords.any { blockCommentNode.text.contains(it, ignoreCase = true) } + } var headerKdoc = firstCodeNode.prevSibling { it.elementType == KDOC } + // Annotations with target`file` can only be placed before `package` directive. var fileAnnotations = node.findChildByType(FILE_ANNOTATION_LIST) + // We also collect all other elements that are placed on top of the file. + // These may be other comments, so we just place them before the code starts. + val otherNodesBeforeCode = firstCodeNode.siblings(forward = false) + .filterNot { + it.isWhiteSpace() || + it == copyrightComment || it == headerKdoc || it == fileAnnotations + } + .toList() + .reversed() // checking order - listOfNotNull(copyrightComment, headerKdoc, fileAnnotations).handleIncorrectOrder({ - getSiblingBlocks(copyrightComment, headerKdoc, fileAnnotations, firstCodeNode) + listOfNotNull(copyrightComment, headerKdoc, fileAnnotations, *otherNodesBeforeCode.toTypedArray()).handleIncorrectOrder({ + getSiblingBlocks(copyrightComment, headerKdoc, fileAnnotations, firstCodeNode, otherNodesBeforeCode) }) { astNode, beforeThisNode -> FILE_INCORRECT_BLOCKS_ORDER.warnAndFix(configRules, emitWarn, isFixMode, astNode.text.lines().first(), astNode.startOffset, astNode) { val result = node.moveChildBefore(astNode, beforeThisNode, true) @@ -237,12 +254,13 @@ class FileStructureRule(private val configRules: List) : Rule("file copyrightComment: ASTNode?, headerKdoc: ASTNode?, fileAnnotations: ASTNode?, - packageDirectiveNode: ASTNode - ): Pair = when (elementType) { - BLOCK_COMMENT -> null to listOfNotNull(headerKdoc, fileAnnotations, packageDirectiveNode).first() - KDOC -> copyrightComment to (fileAnnotations ?: packageDirectiveNode) - FILE_ANNOTATION_LIST -> (headerKdoc ?: copyrightComment) to packageDirectiveNode - else -> error("Only BLOCK_COMMENT, KDOC and FILE_ANNOTATION_LIST are valid inputs.") + firstCodeNode: ASTNode, + otherNodesBeforeFirst: List + ): Pair = when (this) { + copyrightComment -> null to listOfNotNull(headerKdoc, fileAnnotations, otherNodesBeforeFirst.firstOrNull(), firstCodeNode).first() + headerKdoc -> copyrightComment to (fileAnnotations ?: otherNodesBeforeFirst.firstOrNull() ?: firstCodeNode) + fileAnnotations -> (headerKdoc ?: copyrightComment) to (otherNodesBeforeFirst.firstOrNull() ?: firstCodeNode) + else -> (headerKdoc ?: copyrightComment) to firstCodeNode } @Suppress("TYPE_ALIAS") diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt index 6faefd97e3..17601a595b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt @@ -24,6 +24,7 @@ internal const val SET_PREFIX = "set" val emptyBlockList = listOf(LBRACE, WHITE_SPACE, SEMICOLON, RBRACE) val commentType = listOf(BLOCK_COMMENT, EOL_COMMENT, KDOC) +val copyrightWords = setOf("copyright", "版权") internal const val EMPTY_BLOCK_TEXT = "{}" 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 999632ddd4..eae3ab814b 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 @@ -1,8 +1,8 @@ /** * Various utility methods to work with kotlin AST + * FixMe: fix suppressed inspections on KDocs */ -// todo fix inspections on KDocs @file:Suppress("FILE_NAME_MATCH_CLASS", "KDOC_WITHOUT_RETURN_TAG", "KDOC_WITHOUT_PARAM_TAG") package org.cqfn.diktat.ruleset.utils diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleFixTest.kt index f2f544ebb2..59e4074658 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleFixTest.kt @@ -78,4 +78,10 @@ class FileStructureRuleFixTest : FixTestBase("test/paragraph3/file_structure", : fun `should still work with default package and no imports`() { fixAndCompare("NoImportNoPackageExpected.kt", "NoImportNoPackageTest.kt") } + + @Test + @Tag(WarningNames.FILE_UNORDERED_IMPORTS) + fun `should move other comments before package node`() { + fixAndCompare("OtherCommentsExpected.kt", "OtherCommentsTest.kt") + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt index c1fe23eecc..480c269d8e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt @@ -146,4 +146,84 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) { """.trimMargin(), rulesConfigList = rulesConfigListWildCardImports ) } + + @Test + @Tag(WarningNames.FILE_INCORRECT_BLOCKS_ORDER) + fun `should warn if there are other misplaced comments before package - positive example`() { + lintMethod( + """ + |/** + | * This is an example + | */ + | + |// some notes on this file + |package org.cqfn.diktat.example + | + |import org.cqfn.diktat.example.Foo + | + |class Example + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.FILE_INCORRECT_BLOCKS_ORDER) + fun `should warn if there are other misplaced comments before package`() { + lintMethod( + """ + |// some notes on this file + |/** + | * This is an example + | */ + | + |package org.cqfn.diktat.example + | + |import org.cqfn.diktat.example.Foo + | + |class Example + """.trimMargin(), + LintError(1, 1, ruleId, "${FILE_INCORRECT_BLOCKS_ORDER.warnText()} // some notes on this file", true), + LintError(2, 1, ruleId, "${FILE_INCORRECT_BLOCKS_ORDER.warnText()} /**", true), + ) + } + + @Test + @Tag(WarningNames.FILE_INCORRECT_BLOCKS_ORDER) + fun `block comment should be detected as copyright - positive example`() { + lintMethod( + """ + |/* + | * Copyright Example Inc. (c) + | */ + | + |@file:Annotation + | + |package org.cqfn.diktat.example + | + |import org.cqfn.diktat.example.Foo + | + |class Example + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.FILE_INCORRECT_BLOCKS_ORDER) + fun `block comment shouldn't be detected as copyright without keywords`() { + lintMethod( + """ + |/* + | * Just a regular block comment + | */ + |@file:Annotation + | + |package org.cqfn.diktat.example + | + |import org.cqfn.diktat.example.Foo + | + |class Example + """.trimMargin(), + LintError(4, 1, ruleId, "${FILE_INCORRECT_BLOCKS_ORDER.warnText()} @file:Annotation", true) + ) + } } diff --git a/diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsExpected.kt new file mode 100644 index 0000000000..7573a053d1 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsExpected.kt @@ -0,0 +1,11 @@ +/** + * This is an example + */ + +// some notes on this file +// and some more +package org.cqfn.diktat.example + +import org.cqfn.diktat.example.Foo + +class Example diff --git a/diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsTest.kt b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsTest.kt new file mode 100644 index 0000000000..fca05021a6 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OtherCommentsTest.kt @@ -0,0 +1,11 @@ +// some notes on this file +// and some more +/** + * This is an example + */ + +package org.cqfn.diktat.example + +import org.cqfn.diktat.example.Foo + +class Example From 3efb993028b28d52dd70a7788f69f6fa9375bbac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 10:49:37 +0300 Subject: [PATCH 6/7] Bump kotlin.version from 1.4.20 to 1.4.21 (#638) Bumps `kotlin.version` from 1.4.20 to 1.4.21. Updates `kotlin-stdlib-jdk8` from 1.4.20 to 1.4.21 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.4.20...v1.4.21) Updates `kotlin-compiler-embeddable` from 1.4.20 to 1.4.21 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.4.20...v1.4.21) Updates `kotlin-maven-plugin` from 1.4.20 to 1.4.21 Updates `kotlin-maven-serialization` from 1.4.20 to 1.4.21 Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Peter Trifanov --- diktat-gradle-plugin/build.gradle.kts | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index b550f9d3af..fd5bfdb923 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -3,7 +3,7 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurr plugins { `java-gradle-plugin` - kotlin("jvm") version "1.4.20" + kotlin("jvm") version "1.4.21" jacoco id("pl.droidsonroids.jacoco.testkit") version "1.0.7" } @@ -119,4 +119,4 @@ tasks.jacocoTestReport { // xml report is used by codecov xml.isEnabled = true } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index e08a8758cc..5a1c721625 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 1.8 1.8 UTF-8 - 1.4.20 + 1.4.21 true 1.0.1 0.39.0 From 4edbd7899efd471ad2b8b0106d2f9dade13f0a1e Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Mon, 14 Dec 2020 12:01:00 +0300 Subject: [PATCH 7/7] Bugfix. CommentsRule doesn't detect commented out class declaration (#630) * bugfix/comments-rule-detect-class(#462) ### What's done: * Added logic * Added tests --- .../ruleset/rules/comments/CommentsRule.kt | 22 ++++- .../chapter2/comments/CommentedCodeTest.kt | 88 ++++++++++++++++++- .../diktat/ruleset/smoke/DiktatSmokeTest.kt | 3 +- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt index f16c73fd38..2be92b7709 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt @@ -93,7 +93,7 @@ class CommentsRule(private val configRules: List) : Rule("comments" private fun getOffsetsToTextBlocksFromEolComments(node: ASTNode): List> { val comments = node .findAllNodesWithSpecificType(EOL_COMMENT) - .filter { !it.text.contains(eolCommentStart) } + .filter { !it.text.contains(eolCommentStart) || isCodeAfterCommentStart(it.text) } return if (comments.isNotEmpty()) { val result = mutableListOf(mutableListOf(comments.first())) comments @@ -116,10 +116,30 @@ class CommentsRule(private val configRules: List) : Rule("comments" } } + /** + * This is a very rare case. We should check this cases for 4 things: + * + * 1. If it is a class/object at the beginning of the line + * 2. If it is a function + * 3. If it is import/package implementation + * 4. If it is }. This case is used when } goes after one space and it is closing class or fun + */ + private fun isCodeAfterCommentStart(text: String): Boolean { + val textWithoutCommentStartToken = text.removePrefix("//").trim() + return codeFileStartCases.any { textWithoutCommentStartToken.contains(it) } + } + + @Suppress("MaxLineLength") companion object { private val importKeyword = KtTokens.IMPORT_KEYWORD.value private val packageKeyword = KtTokens.PACKAGE_KEYWORD.value private val importOrPackage = """($importKeyword|$packageKeyword) """.toRegex() + private val classRegex = + """^\s*(public|private|protected)*\s*(internal)*\s*(open|data|sealed)*\s*(internal)*\s*(class|object)\s+(\w+)(\(.*\))*(\s*:\s*\w+(\(.*\))*)?\s*\{*$""".toRegex() + private val importOrPackageRegex = """^(import|package)?\s+([a-zA-Z.])+;*$""".toRegex() + private val functionRegex = """^(public|private|protected)*\s*(override|abstract|actual|expect)*\s?fun\s+\w+(\(.*\))?(\s*:\s*\w+)?\s*[{=]${'$'}""".toRegex() + private val rightBraceRegex = """^\s*}$""".toRegex() + private val codeFileStartCases = listOf(classRegex, importOrPackageRegex, functionRegex, rightBraceRegex) private val eolCommentStart = """// \S""".toRegex() } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt index 90dbffdf46..e7a0b8603e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt @@ -96,7 +96,7 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) { @Test @Tag(WarningNames.COMMENTED_OUT_CODE) - fun `Should warn if commented out function is detected (single line comments with surrounding text)`() { + fun `Should warn if commented out function is detected - single line comments with surrounding text`() { lintMethod( """ |import org.junit.Test @@ -153,7 +153,7 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) { @Test @Tag(WarningNames.COMMENTED_OUT_CODE) - fun `Should warn if detects commented out code (example with IDEA style indents)`() { + fun `Should warn if detects commented out code example with IDEA style indents`() { lintMethod( """ |//import org.junit.Ignore @@ -172,4 +172,88 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) { LintError(6, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false) ) } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on class with one space after comment start token`() { + lintMethod( + """ + |// class Test: Exception() + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} class Test: Exception()", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on class with one space after comment start token and 2 modifiers #1`() { + lintMethod( + """ + |// public data class Test(val some: Int): Exception() + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} public data class Test(val some: Int): Exception()", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on class with one space after comment start token and 2 modifiers #2`() { + lintMethod( + """ + |// internal sealed class Test: Exception() + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} internal sealed class Test: Exception()", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on import with one space after comment start token`() { + lintMethod( + """ + |// import some.org + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import some.org", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on package with one space after comment start token`() { + lintMethod( + """ + |// package some.org + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} package some.org", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on function with one space after comment start token - { sign`() { + lintMethod( + """ + |// fun someFunc(name: String): Boolean { + |// val a = 5 + |// } + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun someFunc(name: String): Boolean {", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on function with one space after comment start token - = sign`() { + lintMethod( + """ + |// fun someFunc(name: String): Boolean = + |// name.contains("a") + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun someFunc(name: String): Boolean =", false)) + } + + @Test + @Tag(WarningNames.COMMENTED_OUT_CODE) + fun `should trigger on function with one space after comment start token pulbic modifier`() { + lintMethod( + """ + |// public fun someFunc(name: String): Boolean = + |// name.contains("a") + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} public fun someFunc(name: String): Boolean =", false)) + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt index bbffa5b004..59f3191b01 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -116,7 +116,8 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin", fixAndCompare("Example2Expected.kt", "Example2Test.kt") unfixedLintErrors.assertEquals( LintError(1, 1, "$DIKTAT_RULE_SET_ID:file-naming", "${FILE_NAME_INCORRECT.warnText()} Example2Test.kt_copy", true), // todo this is a false one - LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} there are 2 declared classes and/or objects", false) + LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} there are 2 declared classes and/or objects", false), + LintError(14, 17, "$DIKTAT_RULE_SET_ID:comments", "${Warnings.COMMENTED_OUT_CODE.warnText()} private class Test : RuntimeException()", false) ) }