From e884d001d8bb4c7b423ba419f8d46b7745a4827d Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 30 Mar 2023 18:15:04 +0300 Subject: [PATCH 01/34] ### What's done: * * * --- .../diktat-runner-cli/build.gradle.kts | 21 +++++++++++++++++++ gradle/libs.versions.toml | 11 +++++++--- settings.gradle.kts | 1 + 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 diktat-runner/diktat-runner-cli/build.gradle.kts diff --git a/diktat-runner/diktat-runner-cli/build.gradle.kts b/diktat-runner/diktat-runner-cli/build.gradle.kts new file mode 100644 index 0000000000..12d376f8dc --- /dev/null +++ b/diktat-runner/diktat-runner-cli/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") + id("org.cqfn.diktat.buildutils.code-quality-convention") + id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") +} + +project.description = "This module builds diktat-runner implementation using ktlint as CLI" + +dependencies { + api(projects.diktatRunner.diktatRunnerApi) + implementation(projects.diktatRules) + implementation(projects.diktatRunner.diktatRunnerKtlintEngine) + implementation(libs.kotlinx.cli) + implementation(libs.log4j2.core) + implementation(libs.ktlint.reporter.baseline) + implementation(libs.ktlint.reporter.checkstyle) + implementation(libs.ktlint.reporter.html) + implementation(libs.ktlint.reporter.json) + implementation(libs.ktlint.reporter.plain) + implementation(libs.ktlint.reporter.sarif) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de8fbf5421..61106ad854 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,9 @@ jupiter-itf-extension = "0.12.0" # FIXME: need to migrate to mockito mockito-all = "1.10.19" +# executable jar +log4j = "2.20.0" +kotlinx-cli = "0.3.5" # copied from save-cloud jetbrains-annotations = "24.0.1" @@ -41,8 +44,6 @@ diktat = "1.2.5" jgit = "6.5.0.202303070854-r" mockito = "5.2.0" mockito-kotlin = "4.1.0" -# only in save-cli -log4j = "2.20.0" testcontainers = "1.17.6" okhttp3 = "4.10.0" reckon = "0.16.1" @@ -50,7 +51,6 @@ commons-compress = "1.22" zip4j = "2.11.5" ktoml = "0.4.1" springdoc = "1.6.15" -kotlinx-cli = "0.3.5" spotless = "6.17.0" arrow-kt = "1.1.5" publish = "1.3.0" @@ -107,6 +107,7 @@ ktlint-reporter-sarif = { module = "com.pinterest.ktlint:ktlint-reporter-sarif", ktlint-reporter-json = { module = "com.pinterest.ktlint:ktlint-reporter-json", version.ref = "ktlint" } ktlint-reporter-html = { module = "com.pinterest.ktlint:ktlint-reporter-html", version.ref = "ktlint" } ktlint-reporter-baseline = { module = "com.pinterest.ktlint:ktlint-reporter-baseline", version.ref = "ktlint" } +ktlint-reporter-checkstyle = { module = "com.pinterest.ktlint:ktlint-reporter-checkstyle", version.ref = "ktlint" } sarif4k = { module = "io.github.detekt.sarif4k:sarif4k", version.ref = "sarif4k" } sarif4k-jvm = { module = "io.github.detekt.sarif4k:sarif4k-jvm", version.ref = "sarif4k" } @@ -120,6 +121,10 @@ jbool-expressions = { module = "com.bpodgursky:jbool_expressions", version.ref = # logging kotlin-logging = { module = "io.github.microutils:kotlin-logging", version.ref = "mu-logging" } +log4j2-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j2" } + +# cli +kotlinx-cli = { module = "org.jetbrains.kotlinx:kotlinx-cli", version.ref = "kotlinx-cli" } # testing junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } diff --git a/settings.gradle.kts b/settings.gradle.kts index b9c909cb82..9f6c6db5ed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,6 +40,7 @@ include("diktat-ruleset") include("diktat-test-framework") include("diktat-dev-ksp") include("diktat-runner:diktat-runner-api") +include("diktat-runner:diktat-runner-cli") include("diktat-runner:diktat-runner-ktlint-engine") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") From bbc78fb35f3434583d26fabebfa0f1a01fbd19b0 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Fri, 31 Mar 2023 17:22:58 +0300 Subject: [PATCH 02/34] ### What's done: * added wrappers for Reporter * added wrapper for Baseline * supported processing a bunch of files --- .../plugin/gradle/tasks/DiktatCheckTask.kt | 13 +- .../plugin/gradle/tasks/DiktatFixTask.kt | 13 +- .../plugin/gradle/tasks/DiktatTaskBase.kt | 211 ++++++++--------- .../plugin/gradle/DiktatJavaExecTaskTest.kt | 14 +- .../diktat/plugin/maven/DiktatBaseMojo.kt | 217 +++++++----------- .../cqfn/diktat/plugin/maven/DiktatMojo.kt | 13 +- .../diktat-runner-api/build.gradle.kts | 2 + .../diktat/AbstractDiktatProcessCommand.kt | 112 --------- .../kotlin/org/cqfn/diktat/DiktatProcessor.kt | 110 ++++----- .../org/cqfn/diktat/DiktatProcessorFactory.kt | 29 +++ .../org/cqfn/diktat/api/DiktatBaseline.kt | 34 +++ .../cqfn/diktat/api/DiktatBaselineFactory.kt | 28 +++ .../kotlin/org/cqfn/diktat/api/DiktatMode.kt | 16 ++ .../diktat/api/DiktatProcessorListener.kt | 86 +++++++ .../cqfn/diktat/api/DiktatReporterFactory.kt | 25 ++ .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 86 +++++++ .../org/cqfn/diktat/cli/DiktatProperties.kt | 165 +++++++++++++ .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 42 ++++ .../kotlin/org/cqfn/diktat/util/FileUtils.kt | 36 +++ .../build.gradle.kts | 6 + .../org/cqfn/diktat/DiktatProcessCommand.kt | 85 ------- .../ktlint/DiktatBaselineFactoryImpl.kt | 37 +++ ...LintErrorWrapper.kt => DiktatErrorImpl.kt} | 25 +- .../ktlint/DiktatProcessorFactoryImpl.kt | 42 ++++ .../ktlint/DiktatReporterFactoryImpl.kt | 46 ++++ .../cqfn/diktat/ktlint/DiktatReporterImpl.kt | 34 +++ .../org/cqfn/diktat/ktlint/KtLintUtils.kt | 15 ++ .../diktat/ktlint/LintErrorCallbackWrapper.kt | 2 + .../cqfn/diktat/ktlint/LintErrorReporter.kt | 30 --- .../org/cqfn/diktat/ktlint/ReporterUtil.kt | 91 ++++++++ gradle/libs.versions.toml | 2 +- 31 files changed, 1108 insertions(+), 559 deletions(-) delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/AbstractDiktatProcessCommand.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt create mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt create mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt create mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt create mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/DiktatProcessCommand.kt create mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt rename diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/{LintErrorWrapper.kt => DiktatErrorImpl.kt} (50%) create mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt create mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt create mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt create mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorReporter.kt create mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatCheckTask.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatCheckTask.kt index 9e33195639..d8728ace09 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatCheckTask.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatCheckTask.kt @@ -1,12 +1,14 @@ package org.cqfn.diktat.plugin.gradle.tasks -import org.cqfn.diktat.DiktatProcessCommand +import org.cqfn.diktat.DiktatProcessor +import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.plugin.gradle.DiktatExtension import org.cqfn.diktat.plugin.gradle.DiktatGradlePlugin import org.gradle.api.Project import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternSet +import java.nio.file.Path import javax.inject.Inject /** @@ -16,8 +18,13 @@ abstract class DiktatCheckTask @Inject constructor( extension: DiktatExtension, inputs: PatternFilterable ) : DiktatTaskBase(extension, inputs) { - override fun doRun(diktatCommand: DiktatProcessCommand, formattedContentConsumer: (String) -> Unit) { - diktatCommand.check() + override fun doRun( + diktatProcessor: DiktatProcessor, + listener: DiktatProcessorListener, + files: Sequence, + formattedContentConsumer: (Path, String) -> Unit + ) { + diktatProcessor.checkAll(listener, files) } companion object { diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatFixTask.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatFixTask.kt index c705fb22fe..fa8f3d5964 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatFixTask.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatFixTask.kt @@ -1,12 +1,14 @@ package org.cqfn.diktat.plugin.gradle.tasks -import org.cqfn.diktat.DiktatProcessCommand +import org.cqfn.diktat.DiktatProcessor +import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.plugin.gradle.DiktatExtension import org.cqfn.diktat.plugin.gradle.DiktatGradlePlugin import org.gradle.api.Project import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternSet +import java.nio.file.Path import javax.inject.Inject /** @@ -16,9 +18,12 @@ abstract class DiktatFixTask @Inject constructor( extension: DiktatExtension, inputs: PatternFilterable ) : DiktatTaskBase(extension, inputs) { - override fun doRun(diktatCommand: DiktatProcessCommand, formattedContentConsumer: (String) -> Unit) { - val formattedText = diktatCommand.fix() - formattedContentConsumer(formattedText) + override fun doRun( + diktatProcessor: DiktatProcessor, + listener: DiktatProcessorListener, + files: Sequence, formattedContentConsumer: (Path, String) -> Unit + ) { + diktatProcessor.fixAll(listener, files, formattedContentConsumer) } companion object { diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt index 2a1328bcd7..058076f99c 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt @@ -1,30 +1,22 @@ -@file:Suppress( - "Deprecation" -) - package org.cqfn.diktat.plugin.gradle.tasks -import org.cqfn.diktat.DiktatProcessCommand import org.cqfn.diktat.DiktatProcessor -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.api.DiktatLogLevel -import org.cqfn.diktat.ktlint.LintErrorReporter -import org.cqfn.diktat.ktlint.unwrap +import org.cqfn.diktat.api.DiktatBaseline +import org.cqfn.diktat.api.DiktatBaseline.Companion.skipKnownErrors +import org.cqfn.diktat.api.DiktatBaselineFactory +import org.cqfn.diktat.api.DiktatProcessorListener +import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener +import org.cqfn.diktat.api.DiktatProcessorListener.Companion.countErrorsAsProcessorListener +import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.api.DiktatReporterFactory +import org.cqfn.diktat.ktlint.DiktatBaselineFactoryImpl +import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl +import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl import org.cqfn.diktat.plugin.gradle.DiktatExtension import org.cqfn.diktat.plugin.gradle.DiktatJavaExecTaskBase import org.cqfn.diktat.plugin.gradle.getOutputFile import org.cqfn.diktat.plugin.gradle.getReporterType -import org.cqfn.diktat.plugin.gradle.isSarifReporterActive -import com.pinterest.ktlint.core.Reporter -import com.pinterest.ktlint.core.internal.CurrentBaseline -import com.pinterest.ktlint.core.internal.containsLintError -import com.pinterest.ktlint.core.internal.loadBaseline -import com.pinterest.ktlint.reporter.baseline.BaselineReporter -import com.pinterest.ktlint.reporter.html.HtmlReporter -import com.pinterest.ktlint.reporter.json.JsonReporter -import com.pinterest.ktlint.reporter.plain.PlainReporter -import com.pinterest.ktlint.reporter.sarif.SarifReporter import generated.DIKTAT_VERSION import generated.KTLINT_VERSION import org.gradle.api.DefaultTask @@ -40,9 +32,8 @@ import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.VerificationTask import org.gradle.api.tasks.util.PatternFilterable -import java.io.File -import java.io.FileOutputStream -import java.io.PrintStream +import java.nio.file.Path +import java.util.concurrent.atomic.AtomicInteger /** * A base task to run `diktat` @@ -85,33 +76,44 @@ abstract class DiktatTaskBase( */ @get:Internal internal val diktatProcessor: DiktatProcessor by lazy { - DiktatProcessor.builder() - .diktatRuleSetProvider(extension.diktatConfigFile.toPath()) - .logLevel( - if (extension.debug) { - DiktatLogLevel.DEBUG - } else { - DiktatLogLevel.INFO - } - ) - .build() + DiktatProcessorFactoryImpl().create(extension.diktatConfigFile.toPath()) + } + + /** + * A baseline factory + */ + @get:Internal + internal val baselineFactory: DiktatBaselineFactory by lazy { + DiktatBaselineFactoryImpl() } /** * A baseline loaded from provided file or empty */ @get:Internal - internal val baseline: CurrentBaseline by lazy { - extension.baseline?.let { loadBaseline(it) } - ?: CurrentBaseline(emptyMap(), false) + internal val baseline: DiktatBaseline? by lazy { + extension.baseline?.let { + baselineFactory.tryToLoad( + baselineFile = project.file(it).toPath(), + sourceRootDir = project.projectDir.toPath() + ) + } + } + + /** + * A reporter factory + */ + @get:Internal + internal val reporterFactory: DiktatReporterFactory by lazy { + DiktatReporterFactoryImpl() } /** * A reporter created based on configuration */ @get:Internal - internal val reporter: Reporter by lazy { - resolveReporter(baseline) + internal val processorListener: DiktatProcessorListener by lazy { + createReporter() } init { @@ -136,97 +138,82 @@ abstract class DiktatTaskBase( project.logger.warn("Inputs for $name do not exist, will not run diktat") project.logger.info("Skipping diktat execution") } else { - reporter.beforeAll() - val lintErrorReporter = LintErrorReporter() - actualInputs.files + val loggingListener = object : DiktatProcessorListener.Companion.Empty() { + override fun before(file: Path) { + project.logger.debug("Checking file $file") + } + } + val (baseline, baselineGeneratorListener) = extension.baseline + ?.let { + baselineFactory.tryToLoad( + baselineFile = project.file(it).toPath(), + sourceRootDir = project.projectDir.toPath() + ) + } + ?.let { it to DiktatProcessorListener.empty } + ?: run { + val baselineGenerator = extension.baseline + ?.let { + baselineFactory.generator( + project.file(it).toPath(), + project.rootDir.toPath() + ) + } + ?: DiktatProcessorListener.empty + DiktatBaseline.empty to baselineGenerator + } + val errorCounter = AtomicInteger() + val files = actualInputs.files .also { files -> project.logger.info("Analyzing ${files.size} files with diktat in project ${project.name}") project.logger.debug("Analyzing $files") } - .forEach { file -> - processFile( - file = file, - diktatProcessor = diktatProcessor, - reporter = Reporter.from(reporter, lintErrorReporter) - ) + .asSequence() + .map { it.toPath() } + doRun( + diktatProcessor = diktatProcessor, + listener = DiktatProcessorListener( + processorListener.skipKnownErrors(baseline), + baselineGeneratorListener, + errorCounter.countErrorsAsProcessorListener(), + loggingListener + ), + files = files + ) { file, formattedText -> + val fileName = file.toFile().absolutePath + val fileContent = file.toFile().readText(Charsets.UTF_8) + if (fileContent != formattedText) { + project.logger.info("Original and formatted content differ, writing to $fileName...") + file.toFile().writeText(formattedText, Charsets.UTF_8) } - reporter.afterAll() - if (lintErrorReporter.isNotEmpty() && !ignoreFailures) { - throw GradleException("There are ${lintErrorReporter.errorCount()} lint errors") } - } - } - - private fun processFile( - file: File, - diktatProcessor: DiktatProcessor, - reporter: Reporter - ) { - project.logger.debug("Checking file $file") - reporter.before(file.absolutePath) - val baselineErrors = baseline.baselineRules?.get( - file.relativeTo(project.projectDir).invariantSeparatorsPath - ) ?: emptyList() - val diktatCallback = DiktatCallback { error, isCorrected -> - val ktLintError = error.unwrap() - if (!baselineErrors.containsLintError(ktLintError)) { - reporter.onLintError(file.absolutePath, ktLintError, isCorrected) - } - } - val command = DiktatProcessCommand.builder() - .processor(diktatProcessor) - .file(file.toPath()) - .callback(diktatCallback) - .build() - doRun(command) { formattedText -> - val fileName = file.absolutePath - val fileContent = file.readText(Charsets.UTF_8) - if (fileContent != formattedText) { - project.logger.info("Original and formatted content differ, writing to $fileName...") - file.writeText(formattedText, Charsets.UTF_8) + if (errorCounter.get() > 0 && !ignoreFailures) { + throw GradleException("There are ${errorCounter.get()} lint errors") } } - reporter.after(file.absolutePath) } /** * An abstract method which should be overridden by fix and check tasks * - * @param diktatCommand + * @param diktatProcessor + * @param listener + * @param files * @param formattedContentConsumer */ - protected abstract fun doRun(diktatCommand: DiktatProcessCommand, formattedContentConsumer: (String) -> Unit) + protected abstract fun doRun( + diktatProcessor: DiktatProcessor, + listener: DiktatProcessorListener, + files: Sequence, + formattedContentConsumer: (Path, String) -> Unit + ) - private fun resolveReporter(baselineResults: CurrentBaseline): Reporter { + private fun createReporter(): DiktatReporter { val reporterType = project.getReporterType(extension) - if (isSarifReporterActive(reporterType)) { - // need to set user.home specially for ktlint, so it will be able to put a relative path URI in SARIF - System.setProperty("user.home", project.rootDir.toString()) - } - val output = project.getOutputFile(extension)?.outputStream()?.let { PrintStream(it) } ?: System.`out` - val actualReporter = if (extension.githubActions) { - SarifReporter(output) - } else { - when (reporterType) { - "sarif" -> SarifReporter(output) - "plain" -> PlainReporter(output) - "json" -> JsonReporter(output) - "html" -> HtmlReporter(output) - else -> { - project.logger.warn("Reporter name $reporterType was not specified or is invalid. Falling to 'plain' reporter") - PlainReporter(output) - } - } - } - - return if (baselineResults.baselineGenerationNeeded) { - val baseline = requireNotNull(extension.baseline) { - "baseline is not provided, but baselineGenerationNeeded is true" - } - val baselineReporter = BaselineReporter(PrintStream(FileOutputStream(baseline, true))) - return Reporter.from(actualReporter, baselineReporter) - } else { - actualReporter - } + val (outputStream, closeListener) = project.getOutputFile(extension)?.outputStream()?.let { + it to it.closeAfterAllAsProcessorListener() + } ?: (System.out to DiktatProcessorListener.empty) + val actualReporter = reporterFactory(reporterType, outputStream ?: System.`out`, emptyMap(), project.rootDir.toPath()) + return DiktatProcessorListener(actualReporter, closeListener) } } 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 a363adff6e..ec4e6ba81a 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 @@ -44,7 +44,7 @@ class DiktatJavaExecTaskTest { } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is PlainReporter) + assert(task.processorListener is PlainReporter) } @Test @@ -71,7 +71,7 @@ class DiktatJavaExecTaskTest { } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is PlainReporter) + assert(task.processorListener is PlainReporter) } @Test @@ -108,7 +108,7 @@ class DiktatJavaExecTaskTest { output = "some.txt" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is JsonReporter) + assert(task.processorListener is JsonReporter) } @Test @@ -119,7 +119,7 @@ class DiktatJavaExecTaskTest { reporter = "json" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is JsonReporter) + assert(task.processorListener is JsonReporter) } @Test @@ -131,7 +131,7 @@ class DiktatJavaExecTaskTest { githubActions = true } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is SarifReporter) + assert(task.processorListener is SarifReporter) Assertions.assertEquals( project.rootDir.toString(), System.getProperty("user.home") @@ -149,7 +149,7 @@ class DiktatJavaExecTaskTest { output = "report.json" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is SarifReporter) + assert(task.processorListener is SarifReporter) } @Test @@ -160,7 +160,7 @@ class DiktatJavaExecTaskTest { reporter = "sarif" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.reporter is SarifReporter) + assert(task.processorListener is SarifReporter) Assertions.assertEquals( project.rootDir.toString(), System.getProperty("user.home") 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 7eedfa4cce..ef50312eb2 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 @@ -4,24 +4,17 @@ package org.cqfn.diktat.plugin.maven -import org.cqfn.diktat.DiktatProcessCommand import org.cqfn.diktat.DiktatProcessor -import org.cqfn.diktat.api.DiktatLogLevel -import org.cqfn.diktat.ktlint.LintErrorReporter -import org.cqfn.diktat.ktlint.unwrap -import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript - -import com.pinterest.ktlint.core.LintError -import com.pinterest.ktlint.core.Reporter -import com.pinterest.ktlint.core.RuleExecutionException -import com.pinterest.ktlint.core.internal.CurrentBaseline -import com.pinterest.ktlint.core.internal.containsLintError -import com.pinterest.ktlint.core.internal.loadBaseline -import com.pinterest.ktlint.reporter.baseline.BaselineReporter -import com.pinterest.ktlint.reporter.html.HtmlReporter -import com.pinterest.ktlint.reporter.json.JsonReporter -import com.pinterest.ktlint.reporter.plain.PlainReporter -import com.pinterest.ktlint.reporter.sarif.SarifReporter +import org.cqfn.diktat.api.DiktatBaseline +import org.cqfn.diktat.api.DiktatBaseline.Companion.skipKnownErrors +import org.cqfn.diktat.api.DiktatBaselineFactory +import org.cqfn.diktat.api.DiktatProcessorListener +import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener +import org.cqfn.diktat.api.DiktatProcessorListener.Companion.countErrorsAsProcessorListener +import org.cqfn.diktat.api.DiktatReporterFactory +import org.cqfn.diktat.ktlint.DiktatBaselineFactoryImpl +import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl +import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl import org.apache.maven.execution.MavenSession import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugin.Mojo @@ -29,11 +22,11 @@ import org.apache.maven.plugin.MojoExecutionException import org.apache.maven.plugin.MojoFailureException import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject - import java.io.File import java.io.FileOutputStream -import java.io.PrintStream +import java.io.OutputStream import java.nio.file.Path +import java.util.concurrent.atomic.AtomicInteger import kotlin.io.path.absolutePathString import kotlin.io.path.readText import kotlin.io.path.writeText @@ -103,16 +96,18 @@ abstract class DiktatBaseMojo : AbstractMojo() { private lateinit var mavenSession: MavenSession /** - * @param command instance of [DiktatProcessCommand] used in analysis + * @param processor instance of [DiktatProcessor] used in analysis + * @param listener + * @param files * @param formattedContentConsumer consumer for formatted content of the file */ - abstract fun runAction(command: DiktatProcessCommand, formattedContentConsumer: (String) -> Unit) + abstract fun runAction(processor: DiktatProcessor, listener: DiktatProcessorListener, files: Sequence, formattedContentConsumer: (Path, String) -> Unit) /** * Perform code check using diktat ruleset * * @throws MojoFailureException if code style check was not passed - * @throws MojoExecutionException if [RuleExecutionException] has been thrown + * @throws MojoExecutionException if an exception in __KtLint__ has been thrown */ override fun execute() { val configFile = resolveConfig() @@ -123,66 +118,80 @@ abstract class DiktatBaseMojo : AbstractMojo() { if (excludes.isNotEmpty()) " and excluding $excludes" else "" ) + val sourceRootDir = mavenProject.basedir.parentFile.toPath() val diktatProcessor by lazy { - DiktatProcessor.builder() - .diktatRuleSetProvider(configFile) - .logLevel( - if (debug) DiktatLogLevel.DEBUG else DiktatLogLevel.INFO - ) - .build() + DiktatProcessorFactoryImpl().create(configFile) } - val baselineResults = baseline?.let { loadBaseline(it.absolutePath) } - ?: CurrentBaseline(emptyMap(), false) - val reporterImpl = resolveReporter(baselineResults) - reporterImpl.beforeAll() - - val lintErrorReporter = LintErrorReporter() - inputs - .map(::File) - .forEach { - diktatProcessor.checkDirectory(it, Reporter.from(reporterImpl, lintErrorReporter), baselineResults.baselineRules ?: emptyMap()) + val baselineFactory: DiktatBaselineFactory by lazy { + DiktatBaselineFactoryImpl() + } + val (baselineResults, baselineGenerator) = baseline + ?.let { baselineFactory.tryToLoad(it.toPath(), sourceRootDir) } + ?.let { it to DiktatProcessorListener.empty } + ?: run { + val baselineGenerator = baseline?.let { + baselineFactory.generator(it.toPath(), sourceRootDir) + } ?: DiktatProcessorListener.empty + DiktatBaseline.empty to baselineGenerator } - - reporterImpl.afterAll() - if (lintErrorReporter.isNotEmpty()) { - throw MojoFailureException("There are ${lintErrorReporter.errorCount()} lint errors") + val reporterFactory: DiktatReporterFactory by lazy { + DiktatReporterFactoryImpl() } - } - - private fun resolveReporter(baselineResults: CurrentBaseline): Reporter { - val output = if (this.output.isBlank()) { - if (this.githubActions) { - // need to set user.home specially for ktlint, so it will be able to put a relative path URI in SARIF - System.setProperty("user.home", mavenSession.executionRootDirectory) - PrintStream(FileOutputStream("${mavenProject.basedir}/${mavenProject.name}.sarif", false)) - } else { - System.`out` + val processorListener = resolveListener(reporterFactory, sourceRootDir) + val errorCounter = AtomicInteger() + processorListener.beforeAll() + + runAction( + processor = diktatProcessor, + listener = DiktatProcessorListener( + processorListener.skipKnownErrors(baselineResults), + baselineGenerator, + errorCounter.countErrorsAsProcessorListener() + ), + files = inputs.asSequence().map(::File).map(File::toPath) + ) { file, formattedText -> + val fileName = file.absolutePathString() + val fileContent = file.readText(Charsets.UTF_8) + if (fileContent != formattedText) { + log.info("Original and formatted content differ, writing to $fileName...") + file.writeText(formattedText, Charsets.UTF_8) } - } else { - PrintStream(FileOutputStream(this.output, false)) } + if (errorCounter.get() > 0) { + throw MojoFailureException("There are ${errorCounter.get()} lint errors") + } + } - val actualReporter = if (this.githubActions) { - SarifReporter(output) - } else { - when (this.reporter) { - "sarif" -> SarifReporter(output) - "plain" -> PlainReporter(output) - "json" -> JsonReporter(output) - "html" -> HtmlReporter(output) - else -> { - log.warn("Reporter name ${this.reporter} was not specified or is invalid. Falling to 'plain' reporter") - PlainReporter(output) - } + private fun resolveListener( + reporterFactory: DiktatReporterFactory, + sourceRootDir: Path, + ): DiktatProcessorListener { + val reporterType = getReporterType() + val (outputStream, closeListener) = getReporterOutput() + ?.let { it to it.closeAfterAllAsProcessorListener() } + ?: run { + System.`out` to DiktatProcessorListener.empty } - } + val actualReporter = reporterFactory(reporterType, outputStream, emptyMap(), sourceRootDir) - return if (baselineResults.baselineGenerationNeeded) { - val baselineReporter = BaselineReporter(PrintStream(FileOutputStream(baseline, true))) - return Reporter.from(actualReporter, baselineReporter) - } else { - actualReporter - } + return DiktatProcessorListener(actualReporter, closeListener) + } + + private fun getReporterType(): String = if (githubActions) { + "sarif" + } else if (reporter in setOf("sarif", "plain", "json", "html")) { + reporter + } else { + log.warn("Reporter name ${this.reporter} was not specified or is invalid. Falling to 'plain' reporter") + "plain" + } + + private fun getReporterOutput(): OutputStream? = if (output.isNotBlank()) { + FileOutputStream(this.output, false) + } else if (githubActions) { + FileOutputStream("${mavenProject.basedir}/${mavenProject.name}.sarif", false) + } else { + null } /** @@ -204,66 +213,4 @@ abstract class DiktatBaseMojo : AbstractMojo() { } .absolutePath } - - /** - * @throws MojoExecutionException if [RuleExecutionException] has been thrown by ktlint - */ - @Suppress("TYPE_ALIAS") - private fun DiktatProcessor.checkDirectory( - directory: File, - reporter: Reporter, - baselineRules: Map>, - ) { - val (excludedDirs, excludedFiles) = excludes.map(::File).partition { it.isDirectory } - directory - .walk() - .filter { file -> - file.isDirectory || file.toPath().isKotlinCodeOrScript() - } - .filter { it.isFile } - .filterNot { file -> file in excludedFiles || excludedDirs.any { file.startsWith(it) } } - .forEach { file -> - log.debug("Checking file $file") - try { - reporter.before(file.absolutePath) - checkFile( - file.toPath(), - reporter, - baselineRules.getOrDefault( - file.relativeTo(mavenProject.basedir.parentFile).invariantSeparatorsPath, - emptyList() - ), - ) - reporter.after(file.absolutePath) - } catch (e: RuleExecutionException) { - log.error("Unhandled exception during rule execution: ", e) - throw MojoExecutionException("Unhandled exception during rule execution", e) - } - } - } - - private fun DiktatProcessor.checkFile( - file: Path, - reporter: Reporter, - baselineErrors: List, - ) { - val command = DiktatProcessCommand.builder() - .processor(this) - .file(file) - .callback { error, isCorrected -> - val ktLintError = error.unwrap() - if (!baselineErrors.containsLintError(ktLintError)) { - reporter.onLintError(file.absolutePathString(), ktLintError, isCorrected) - } - } - .build() - runAction(command) { formattedText -> - val fileName = file.absolutePathString() - val fileContent = file.readText(Charsets.UTF_8) - if (fileContent != formattedText) { - log.info("Original and formatted content differ, writing to $fileName...") - file.writeText(formattedText, Charsets.UTF_8) - } - } - } } 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 9934bf3350..76dcb21798 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.DiktatProcessCommand +import org.cqfn.diktat.DiktatProcessor +import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider import org.apache.maven.plugins.annotations.Mojo +import java.nio.file.Path /** * Main [Mojo] that call [DiktatRuleSetProvider]'s rules on [inputs] files @@ -15,8 +17,8 @@ import org.apache.maven.plugins.annotations.Mojo @Mojo(name = "check") @Suppress("unused") class DiktatCheckMojo : DiktatBaseMojo() { - override fun runAction(command: DiktatProcessCommand, formattedContentConsumer: (String) -> Unit) { - command.check() + override fun runAction(processor: DiktatProcessor, listener: DiktatProcessorListener, files: Sequence, formattedContentConsumer: (Path, String) -> Unit) { + processor.checkAll(listener, files) } } @@ -27,8 +29,7 @@ class DiktatCheckMojo : DiktatBaseMojo() { @Mojo(name = "fix") @Suppress("unused") class DiktatFixMojo : DiktatBaseMojo() { - override fun runAction(command: DiktatProcessCommand, formattedContentConsumer: (String) -> Unit) { - val formattedText = command.fix() - formattedContentConsumer(formattedText) + override fun runAction(processor: DiktatProcessor, listener: DiktatProcessorListener, files: Sequence, formattedContentConsumer: (Path, String) -> Unit) { + processor.fixAll(listener, files, formattedContentConsumer) } } diff --git a/diktat-runner/diktat-runner-api/build.gradle.kts b/diktat-runner/diktat-runner-api/build.gradle.kts index eaad652c9a..549187cefe 100644 --- a/diktat-runner/diktat-runner-api/build.gradle.kts +++ b/diktat-runner/diktat-runner-api/build.gradle.kts @@ -1,7 +1,9 @@ +@Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") + alias(libs.plugins.kotlin.plugin.serialization) } project.description = "This module builds diktat-runner-api" diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/AbstractDiktatProcessCommand.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/AbstractDiktatProcessCommand.kt deleted file mode 100644 index 98b6c05338..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/AbstractDiktatProcessCommand.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.cqfn.diktat - -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.ruleset.utils.isKotlinScript -import java.nio.file.Path -import kotlin.io.path.readText - -/** - * An abstract implementation for command to run `diktat` - * - * @property processor - * @property file - * @property fileContent - * @property isScript - * @property callback - */ -abstract class AbstractDiktatProcessCommand( - protected val processor: DiktatProcessor, - protected val file: Path, - protected val fileContent: String, - protected val isScript: Boolean, - protected val callback: DiktatCallback, -) { - /** - * Run `diktat fix` using parameters from current command - * - * @return result of `diktat fix` - */ - abstract fun fix(): String - - /** - * Run `diktat check` using parameters from current command - */ - abstract fun check() - - /** - * Builder for [AbstractDiktatProcessCommand] - */ - abstract class Builder { - private var processor: DiktatProcessor? = null - private var file: Path? = null - private var fileContent: String? = null - private var isScript: Boolean? = null - private var callback: DiktatCallback? = null - - /** - * @param processor - * @return updated builder - */ - fun processor(processor: DiktatProcessor) = apply { this.processor = processor } - - /** - * @param file - * @return updated builder - */ - fun file(file: Path) = apply { this.file = file } - - /** - * @param fileContent - * @return updated builder - */ - fun fileContent(fileContent: String) = apply { this.fileContent = fileContent } - - /** - * @param isScript - * @return updated builder - */ - fun isScript(isScript: Boolean) = apply { this.isScript = isScript } - - /** - * @param callback - * @return updated builder - */ - fun callback(callback: DiktatCallback) = apply { this.callback = callback } - - /** - * @return built [C] - */ - fun build(): C { - val resolvedFile = requireNotNull(file) { - "file is required" - } - return doBuild( - processor = requireNotNull(processor) { - "processor is required" - }, - file = resolvedFile, - fileContent = fileContent ?: resolvedFile.readText(Charsets.UTF_8), - isScript = isScript ?: resolvedFile.isKotlinScript(), - callback = requireNotNull(callback) { - "callback is required" - }, - ) - } - - /** - * @param processor - * @param file - * @param fileContent - * @param isScript - * @param callback - * @return [C] is built using values from builder - */ - abstract fun doBuild( - processor: DiktatProcessor, - file: Path, - fileContent: String, - isScript: Boolean, - callback: DiktatCallback, - ): C - } -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt index 69475f996f..af942c488a 100644 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt @@ -1,68 +1,72 @@ package org.cqfn.diktat -import org.cqfn.diktat.api.DiktatLogLevel -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider +import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.api.DiktatProcessorListener import java.nio.file.Path -import kotlin.io.path.absolutePathString /** * Processor to run `diktat` - * - * @property diktatRuleSetProvider - * @property logLevel */ -@Suppress("USE_DATA_CLASS") // to hide `copy` method -class DiktatProcessor private constructor( - val diktatRuleSetProvider: DiktatRuleSetProvider, - val logLevel: DiktatLogLevel, -) { +abstract class DiktatProcessor { /** - * Builder for [DiktatProcessCommand] + * Run `diktat fix` on provided [file] using [callback] for detected errors and returned formatted file content. * - * @property diktatRuleSetProvider - * @property logLevel + * @param file + * @param callback + * @return result of `diktat fix` */ - class Builder internal constructor( - private var diktatRuleSetProvider: DiktatRuleSetProvider? = null, - private var logLevel: DiktatLogLevel = DiktatLogLevel.INFO, - ) { - /** - * @param diktatRuleSetProvider - * @return updated builder - */ - fun diktatRuleSetProvider(diktatRuleSetProvider: DiktatRuleSetProvider) = apply { this.diktatRuleSetProvider = diktatRuleSetProvider } - - /** - * @param configFile a config file to load [DiktatRuleSetProvider] - * @return updated builder - */ - fun diktatRuleSetProvider(configFile: String) = diktatRuleSetProvider(DiktatRuleSetProvider(configFile)) - - /** - * @param configFile a config file to load [DiktatRuleSetProvider] - * @return updated builder - */ - fun diktatRuleSetProvider(configFile: Path) = diktatRuleSetProvider(configFile.absolutePathString()) - - /** - * @param logLevel - * @return updated builder - */ - fun logLevel(logLevel: DiktatLogLevel) = apply { this.logLevel = logLevel } + abstract fun fix(file: Path, callback: DiktatCallback): String - /** - * @return built [DiktatProcessCommand] - */ - fun build(): DiktatProcessor = DiktatProcessor( - diktatRuleSetProvider = diktatRuleSetProvider ?: DiktatRuleSetProvider(), - logLevel = logLevel, - ) + /** + * Run `diktat fix` for all [files] using [listener] during of processing and [formattedCodeHandler] to handle result of `diktat fix`. + * + * @param listener a listener which is called during processing. + * @param files + * @param formattedCodeHandler + */ + fun fixAll( + listener: DiktatProcessorListener = DiktatProcessorListener.empty, + files: Sequence, + formattedCodeHandler: (Path, String) -> Unit, + ) { + listener.beforeAll() + files.forEach { file -> + listener.before(file) + val formattedCode = fix(file) { error, isCorrected -> + listener.onError(file, error, isCorrected) + } + formattedCodeHandler(file, formattedCode) + listener.after(file) + } + listener.afterAll() } - companion object { - /** - * @return a builder for [DiktatProcessor] - */ - fun builder(): Builder = Builder() + /** + * Run `diktat check` on provided [file] using [callback] for detected errors. + * + * @param file + * @param callback + */ + abstract fun check(file: Path, callback: DiktatCallback) + + /** + * Run `diktat check` for all [files] using [listener] during of processing. + * + * @param listener a listener which is called during processing. + * @param files + */ + fun checkAll( + listener: DiktatProcessorListener = DiktatProcessorListener.empty, + files: Sequence, + ) { + listener.beforeAll() + files.forEach { file -> + listener.before(file) + check(file) { error, isCorrected -> + listener.onError(file, error, isCorrected) + } + listener.after(file) + } + listener.afterAll() } } diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt new file mode 100644 index 0000000000..42001b0046 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt @@ -0,0 +1,29 @@ +package org.cqfn.diktat + +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider +import java.nio.file.Path +import kotlin.io.path.absolutePathString + +/** + * A factory to create [DiktatProcessor] using [DiktatRuleSetProvider] + */ +@FunctionalInterface +fun interface DiktatProcessorFactory : Function1 { + /** + * @param diktatRuleSetProvider + * @return created [DiktatProcessor] using [diktatRuleSetProvider] + */ + override fun invoke(diktatRuleSetProvider: DiktatRuleSetProvider): DiktatProcessor + + /** + * @param configFile a path to file with configuration for diktat (`diktat-analysis.yml`) + * @return created [DiktatProcessor] using [configFile] + */ + fun create(configFile: String): DiktatProcessor = invoke(DiktatRuleSetProvider(configFile)) + + /** + * @param configFile a file with configuration for diktat (`diktat-analysis.yml`) + * @return created [DiktatProcessor] using [configFile] + */ + fun create(configFile: Path): DiktatProcessor = create(configFile.absolutePathString()) +} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt new file mode 100644 index 0000000000..1f7fbb9e1e --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt @@ -0,0 +1,34 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path + +/** + * A base interface for Baseline + */ +fun interface DiktatBaseline { + /** + * @param file + * @return a set of [DiktatError] found in baseline by [file] + */ + fun errorsByFile(file: Path): Set + + companion object { + /** + * Empty [DiktatBaseline] + */ + val empty: DiktatBaseline = DiktatBaseline { _ -> emptySet() } + + fun DiktatProcessorListener.skipKnownErrors(baseline: DiktatBaseline): DiktatProcessorListener = object : DiktatProcessorListener { + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) { + if (!baseline.errorsByFile(file).contains(error)) { + this@skipKnownErrors.onError(file, error, isCorrected) + } + } + + override fun beforeAll() = this@skipKnownErrors.beforeAll() + override fun before(file: Path) = this@skipKnownErrors.before(file) + override fun after(file: Path) = this@skipKnownErrors.after(file) + override fun afterAll() = this@skipKnownErrors.afterAll() + } + } +} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt new file mode 100644 index 0000000000..9276e0ae38 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt @@ -0,0 +1,28 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path + +/** + * A factory to load or generate [DiktatBaseline] + */ +interface DiktatBaselineFactory { + /** + * @param baselineFile + * @param sourceRootDir a dir to detect relative path for processing files + * @return Loaded [DiktatBaseline] from [baselineFile] or null if it gets an error in loading + */ + fun tryToLoad( + baselineFile: Path, + sourceRootDir: Path, + ): DiktatBaseline? + + /** + * @param baselineFile + * @param sourceRootDir a dir to detect relative path for processing files + * @return [DiktatProcessorListener] which generates baseline in [baselineFile] + */ + fun generator( + baselineFile: Path, + sourceRootDir: Path, + ): DiktatProcessorListener +} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt new file mode 100644 index 0000000000..aabe4ea9c5 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Mode of `diktat` + */ +@Serializable +enum class DiktatMode { + @SerialName("check") + CHECK, + @SerialName("fix") + FIX, + ; +} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt new file mode 100644 index 0000000000..513019f8d8 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt @@ -0,0 +1,86 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path +import java.util.concurrent.atomic.AtomicInteger + +/** + * A listener for [org.cqfn.diktat.DiktatProcessor] + */ +interface DiktatProcessorListener { + /** + * Called once, before [org.cqfn.diktat.DiktatProcessor] starts process a bunch of files. + */ + fun beforeAll(): Unit = Unit + + /** + * Called before each file when [org.cqfn.diktat.DiktatProcessor] starts to process it. + * + * @param file + */ + fun before(file: Path): Unit = Unit + + /** + * Called on each error when [org.cqfn.diktat.DiktatProcessor] detects such one. + * + * @param file + * @param error + * @param isCorrected + */ + fun onError(file: Path, error: DiktatError, isCorrected: Boolean) + + /** + * Called after each file when [org.cqfn.diktat.DiktatProcessor] finished to process it. + * + * @param file + */ + fun after(file: Path): Unit = Unit + + /** + * Called once, after the processing of [org.cqfn.diktat.DiktatProcessor] finished. + */ + fun afterAll(): Unit = Unit + + companion object { + /** + * An empty implementation of [DiktatProcessorListener] + */ + open class Empty : DiktatProcessorListener { + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = Unit + } + + /** + * An instance of [DiktatProcessorListener.Empty] + */ + val empty = Empty() + + /** + * @param listeners + * @return a single [DiktatProcessorListener] which uses all provided [listeners] + */ + operator fun invoke(vararg listeners: DiktatProcessorListener): DiktatProcessorListener = object : DiktatProcessorListener { + override fun beforeAll() = listeners.forEach(DiktatProcessorListener::beforeAll) + override fun before(file: Path) = listeners.forEach { it.before(file) } + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = listeners.forEach { it.onError(file, error, isCorrected) } + override fun after(file: Path) = listeners.forEach { it.after(file) } + override fun afterAll() = listeners.forEach(DiktatProcessorListener::afterAll) + } + + /** + * An implementation of [DiktatProcessorListener] which counts [DiktatError]s + */ + fun AtomicInteger.countErrorsAsProcessorListener(): DiktatProcessorListener = object : DiktatProcessorListener { + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) { + incrementAndGet() + } + } + + /** + * An implementation of [DiktatProcessorListener] which closes [AutoCloseable] at the end + */ + fun AutoCloseable.closeAfterAllAsProcessorListener(): DiktatProcessorListener = object : Empty() { + override fun afterAll() { + this@closeAfterAllAsProcessorListener.close() + } + } + } +} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt new file mode 100644 index 0000000000..93e48e275a --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt @@ -0,0 +1,25 @@ +package org.cqfn.diktat.api + +import java.io.OutputStream +import java.nio.file.Path + +typealias DiktatReporter = DiktatProcessorListener + +/** + * A factory to create [DiktatReporter] + */ +fun interface DiktatReporterFactory : Function4, Path, DiktatReporter> { + /** + * @param id ID of [DiktatReporter] + * @param outputStream + * @param extraProperties extra properties which can be required to create [DiktatReporter] + * @param sourceRootDir a dir to detect relative path for processing files + * @return created [DiktatReporter] + */ + override operator fun invoke( + id: String, + outputStream: OutputStream, + extraProperties: Map, + sourceRootDir: Path, + ): DiktatReporter +} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt new file mode 100644 index 0000000000..5c87f8dd0d --- /dev/null +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -0,0 +1,86 @@ +/** + * The file contains main method + */ + +package org.cqfn.diktat + +import org.cqfn.diktat.api.DiktatError +import org.cqfn.diktat.api.DiktatMode +import org.cqfn.diktat.cli.DiktatProperties +import org.cqfn.diktat.common.utils.loggerWithKtlintConfig +import org.cqfn.diktat.ktlint.unwrap +import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript +import org.cqfn.diktat.util.tryToPathIfExists +import org.cqfn.diktat.util.walkByGlob +import mu.KotlinLogging +import java.nio.file.Paths +import kotlin.io.path.absolutePathString +import kotlin.io.path.writeText + +@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") +private val log = KotlinLogging.loggerWithKtlintConfig {} + +typealias DiktatErrorWithCorrectionInfo = Pair + +@Suppress( + "LongMethod", + "TOO_LONG_FUNCTION" +) +fun main(args: Array) { + val properties = DiktatProperties.parse(args) + properties.configureLogger() + + log.debug { + "Loading diktatRuleSet using config ${properties.config}" + } + val diktatProcessor = DiktatProcessor.builder() + .diktatRuleSetProvider(properties.config) + .build() + val reporter = properties.reporter() + reporter.beforeAll() + + log.debug { + "Resolving files by patterns: ${properties.patterns}" + } + val currentFolder = Paths.get(".") + properties.patterns + .asSequence() + .flatMap { pattern -> + pattern.tryToPathIfExists()?.let { sequenceOf(it) } + ?: currentFolder.walkByGlob(pattern) + } + .filter { file -> file.isKotlinCodeOrScript() } + .distinct() + .map { it.normalize() } + .map { file -> + log.debug { + "Start processing the file: $file" + } + val result: MutableList = mutableListOf() + DiktatProcessCommand.builder() + .processor(diktatProcessor) + .file(file) + .callback { error, isCorrected -> + result.add(error to isCorrected) + } + .build() + .let { command -> + when (properties.mode) { + DiktatMode.CHECK -> command.check() + DiktatMode.FIX -> { + val formattedFileContent = command.fix() + file.writeText(formattedFileContent, Charsets.UTF_8) + } + } + } + file to result + } + .forEach { (file, result) -> + reporter.before(file.absolutePathString()) + result.forEach { (error, isCorrected) -> + reporter.onLintError(file.absolutePathString(), error.unwrap(), isCorrected) + } + reporter.after(file.absolutePathString()) + } + reporter.afterAll() +} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt new file mode 100644 index 0000000000..8f289f702e --- /dev/null +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -0,0 +1,165 @@ +package org.cqfn.diktat.cli + +import org.cqfn.diktat.api.DiktatMode +import org.cqfn.diktat.common.config.rules.DIKTAT +import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF +import org.cqfn.diktat.ktlint.buildReporter +import org.cqfn.diktat.util.colorName +import org.cqfn.diktat.util.reporterProviderId +import com.pinterest.ktlint.core.Reporter +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.LoggerContext +import org.slf4j.event.Level +import kotlin.system.exitProcess +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.default +import kotlinx.cli.vararg + +/** + * @property config path to `diktat-analysis.yml` + * @property mode mode of `diktat` + * @property reporterProviderId + * @property output + * @property patterns + */ +data class DiktatProperties( + val config: String, + val mode: DiktatMode, + val reporterProviderId: String, + val output: String?, + private val groupByFileInPlain: Boolean, + private val colorNameInPlain: String?, + private val logLevel: Level, + val patterns: List, +) { + /** + * @return a configured [Reporter] + */ + fun reporter(): Reporter = buildReporter( + reporterProviderId, output, colorNameInPlain, groupByFileInPlain, mode + ) + + /** + * Configure logger level using [logLevel] + */ + fun configureLogger() { + // set log level + LogManager.getContext(false) + .let { it as LoggerContext } + .also { ctx -> + ctx.configuration.rootLogger.level = when (logLevel) { + Level.ERROR -> org.apache.logging.log4j.Level.ERROR + Level.WARN -> org.apache.logging.log4j.Level.WARN + Level.INFO -> org.apache.logging.log4j.Level.INFO + Level.DEBUG -> org.apache.logging.log4j.Level.DEBUG + Level.TRACE -> org.apache.logging.log4j.Level.TRACE + } + } + .updateLoggers() + } + + companion object { + /** + * @param args cli arguments + * @return parsed [DiktatProperties] + */ + @Suppress( + "LongMethod", + "TOO_LONG_FUNCTION" + ) + fun parse(args: Array): DiktatProperties { + val parser = ArgParser(DIKTAT) + val config: String by parser.option( + type = ArgType.String, + fullName = "config", + shortName = "c", + description = "Specify the location of the YAML configuration file. By default, $DIKTAT_ANALYSIS_CONF in the current directory is used.", + ).default(DIKTAT_ANALYSIS_CONF) + val mode: DiktatMode by parser.option( + type = ArgType.Choice(), + fullName = "mode", + shortName = "m", + description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." + ).default(DiktatMode.CHECK) + val reporterProviderId: String by parser.reporterProviderId() + val output: String? by parser.option( + type = ArgType.String, + fullName = "output", + shortName = "o", + description = "Redirect the reporter output to a file.", + ) + val groupByFileInPlain: Boolean by parser.option( + type = ArgType.Boolean, + fullName = "plain-group-by-file", + shortName = null, + description = "A flag for plain reporter" + ).default(false) + val colorName: String? by parser.colorName() + val logLevel: Level by parser.option( + type = ArgType.Choice(), + fullName = "log-level", + shortName = "l", + description = "Enable the output with specific level", + ).default(Level.INFO) + val patterns: List by parser.argument( + type = ArgType.String, + description = "A list of files to process by diktat" + ).vararg() + + parser.addOptionAndShowResourceWithExit( + fullName = "version", + shortName = "V", + description = "Output version information and exit.", + args = args, + resourceName = "META-INF/diktat/version" + ) + parser.addOptionAndShowResourceWithExit( + fullName = "license", + shortName = null, + description = "Display the license and exit.", + args = args, + resourceName = "META-INF/diktat/LICENSE", + ) + + parser.parse(args) + return DiktatProperties( + config = config, + mode = mode, + reporterProviderId = reporterProviderId, + output = output, + groupByFileInPlain = groupByFileInPlain, + colorNameInPlain = colorName, + logLevel = logLevel, + patterns = patterns, + ) + } + + private fun ArgParser.addOptionAndShowResourceWithExit( + fullName: String, + shortName: String?, + description: String, + args: Array, + resourceName: String, + ) { + // add here to print in help + option( + type = ArgType.Boolean, + fullName = fullName, + shortName = shortName, + description = description + ) + if (args.contains("--$fullName") || shortName?.let { args.contains("-$it") } == true) { + @Suppress("DEBUG_PRINT", "ForbiddenMethodCall") + print(readFromResource(resourceName)) + exitProcess(0) + } + } + + private fun readFromResource(resourceName: String): String = DiktatProperties::class.java + .classLoader + .getResource(resourceName) + ?.readText() + ?: error("Resource $resourceName not found") + } +} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt new file mode 100644 index 0000000000..d1da8ec262 --- /dev/null +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -0,0 +1,42 @@ +/** + * This class contains util methods to operate with kotlinx.cli.* + */ + +package org.cqfn.diktat.util + +import org.cqfn.diktat.ktlint.colorNamesForPlainReporter +import org.cqfn.diktat.ktlint.plainReporterProvider +import org.cqfn.diktat.ktlint.reporterProviders +import com.pinterest.ktlint.core.ReporterProvider +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.default + +/** + * @return a single [ReporterProvider] as parsed cli arg + */ +internal fun ArgParser.reporterProviderId() = option( + type = ArgType.Choice( + choices = reporterProviders.keys.toList(), + toVariant = { it }, + variantToString = { it }, + ), + fullName = "reporter", + shortName = "r", + description = "The reporter to use" +) + .default(plainReporterProvider.id) + +/** + * @return a single and optional color name as parsed cli args + */ +internal fun ArgParser.colorName() = this.option( + type = ArgType.Choice( + choices = colorNamesForPlainReporter, + toVariant = { it }, + variantToString = { it }, + ), + fullName = "plain-color", + shortName = null, + description = "Colorize the output.", +) diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt new file mode 100644 index 0000000000..22f5027272 --- /dev/null +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt @@ -0,0 +1,36 @@ +/** + * This class contains util methods to operate with java.nio.file.Path + */ + +package org.cqfn.diktat.util + +import java.nio.file.InvalidPathException +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.PathWalkOption +import kotlin.io.path.exists +import kotlin.io.path.walk + +/** + * Create a matcher and return a filter that uses it. + * + * @param glob glob pattern to filter files + * @return a sequence of files which matches to [glob] + */ +@OptIn(ExperimentalPathApi::class) +fun Path.walkByGlob(glob: String): Sequence = + fileSystem.getPathMatcher("glob:$glob") + .let { matcher -> + this.walk(PathWalkOption.INCLUDE_DIRECTORIES) + .filter { matcher.matches(it) } + } + +/** + * @return path or null if path is invalid or doesn't exist + */ +fun String.tryToPathIfExists(): Path? = try { + Paths.get(this).takeIf { it.exists() } +} catch (e: InvalidPathException) { + null +} diff --git a/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts b/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts index 9e3534faa5..a32060b715 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts +++ b/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts @@ -10,4 +10,10 @@ dependencies { api(projects.diktatRunner.diktatRunnerApi) implementation(projects.diktatRules) implementation(libs.ktlint.core) + implementation(libs.ktlint.reporter.baseline) + implementation(libs.ktlint.reporter.checkstyle) + implementation(libs.ktlint.reporter.html) + implementation(libs.ktlint.reporter.json) + implementation(libs.ktlint.reporter.plain) + implementation(libs.ktlint.reporter.sarif) } diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/DiktatProcessCommand.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/DiktatProcessCommand.kt deleted file mode 100644 index 04a605af5d..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/DiktatProcessCommand.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.cqfn.diktat - -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.api.DiktatLogLevel -import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint -import org.cqfn.diktat.ktlint.unwrap -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.api.EditorConfigOverride -import java.nio.file.Path -import kotlin.io.path.absolutePathString - -/** - * Command to run `diktat` - * - * @property processor - * @property file - * @property fileContent - * @property isScript - * @property callback - */ -class DiktatProcessCommand private constructor( - processor: DiktatProcessor, - file: Path, - fileContent: String, - isScript: Boolean, - callback: DiktatCallback, -) : AbstractDiktatProcessCommand( - processor, - file, - fileContent, - isScript, - callback, -) { - /** - * Run `diktat fix` using parameters from current command - * - * @return result of `diktat fix` - */ - override fun fix(): String = KtLint.format(ktLintParams()) - - /** - * Run `diktat check` using parameters from current command - */ - override fun check(): Unit = KtLint.lint(ktLintParams()) - - @Suppress("DEPRECATION") - private fun ktLintParams(): KtLint.ExperimentalParams = KtLint.ExperimentalParams( - fileName = file.absolutePathString(), - text = fileContent, - ruleSets = setOf(processor.diktatRuleSetProvider.toKtLint().get()), - userData = emptyMap(), - cb = callback.unwrap(), - script = isScript, - editorConfigPath = null, - debug = processor.logLevel == DiktatLogLevel.DEBUG, - editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride, - isInvokedFromCli = false - ) - - /** - * Builder for [DiktatProcessCommand] - */ - class Builder internal constructor() : AbstractDiktatProcessCommand.Builder() { - override fun doBuild( - processor: DiktatProcessor, - file: Path, - fileContent: String, - isScript: Boolean, - callback: DiktatCallback, - ): DiktatProcessCommand = DiktatProcessCommand( - processor = processor, - file = file, - fileContent = fileContent, - isScript = isScript, - callback = callback, - ) - } - - companion object { - /** - * @return a builder for [DiktatProcessCommand] - */ - fun builder(): Builder = Builder() - } -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt new file mode 100644 index 0000000000..ecb24b080f --- /dev/null +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt @@ -0,0 +1,37 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatBaseline +import org.cqfn.diktat.api.DiktatBaselineFactory +import org.cqfn.diktat.api.DiktatProcessorListener +import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener +import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap +import com.pinterest.ktlint.core.internal.loadBaseline +import com.pinterest.ktlint.reporter.baseline.BaselineReporter +import java.io.PrintStream +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.outputStream + +class DiktatBaselineFactoryImpl : DiktatBaselineFactory { + override fun tryToLoad(baselineFile: Path, sourceRootDir: Path): DiktatBaseline? { + return loadBaseline(baselineFile.absolutePathString()) + .takeUnless { it.baselineGenerationNeeded } + ?.let { ktLintBaseline -> + DiktatBaseline { file -> + ktLintBaseline.baselineRules + ?.get(file.relativePathStringTo(sourceRootDir)) + .orEmpty() + .map { it.wrap() } + .toSet() + } + } + } + + override fun generator(baselineFile: Path, sourceRootDir: Path): DiktatProcessorListener { + val outputStream = baselineFile.outputStream() + return DiktatProcessorListener( + BaselineReporter(PrintStream(outputStream)).wrap(sourceRootDir), + outputStream.closeAfterAllAsProcessorListener() + ) + } +} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt similarity index 50% rename from diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorWrapper.kt rename to diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt index b293a1cee5..52348ed0ca 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorWrapper.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt @@ -8,7 +8,7 @@ import com.pinterest.ktlint.core.LintError * * @property lintError */ -data class LintErrorWrapper( +data class DiktatErrorImpl( val lintError: LintError ) : DiktatError { override fun getLine(): Int = lintError.line @@ -20,15 +20,18 @@ data class LintErrorWrapper( override fun getDetail(): String = lintError.detail override fun canBeAutoCorrected(): Boolean = lintError.canBeAutoCorrected -} -/** - * @return [DiktatError] from KtLint [LintError] - */ -fun LintError.wrap(): DiktatError = LintErrorWrapper(this) + companion object { + /** + * @return [DiktatError] from KtLint [LintError] + */ + fun LintError.wrap(): DiktatError = DiktatErrorImpl(this) + + /** + * @return KtLint [LintError] from [DiktatError] or exception + */ + fun DiktatError.unwrap(): LintError = (this as? DiktatErrorImpl)?.lintError + ?: error("Unsupported wrapper of ${DiktatError::class.java.simpleName}: ${this::class.java.canonicalName}") + } +} -/** - * @return KtLint [LintError] from [DiktatError] or exception - */ -fun DiktatError.unwrap(): LintError = (this as? LintErrorWrapper)?.lintError - ?: error("Unsupported wrapper of ${DiktatError::class.java.simpleName}: ${this::class.java.canonicalName}") diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt new file mode 100644 index 0000000000..f52c8ba301 --- /dev/null +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt @@ -0,0 +1,42 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.DiktatProcessor +import org.cqfn.diktat.DiktatProcessorFactory +import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider +import org.cqfn.diktat.ruleset.utils.LintErrorCallback +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.api.EditorConfigOverride +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.readText + +/** + * A factory to create [DiktatProcessor] using [DiktatRuleSetProvider] using `KtLint` + */ +class DiktatProcessorFactoryImpl : DiktatProcessorFactory { + override fun invoke(diktatRuleSetProvider: DiktatRuleSetProvider): DiktatProcessor = object : DiktatProcessor() { + override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(ktLintParams(diktatRuleSetProvider, file, callback.unwrap())) + override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(ktLintParams(diktatRuleSetProvider, file, callback.unwrap())) + } + + @Suppress("DEPRECATION") + private fun ktLintParams( + diktatRuleSetProvider: DiktatRuleSetProvider, + file: Path, + callback: LintErrorCallback, + ): KtLint.ExperimentalParams = KtLint.ExperimentalParams( + fileName = file.absolutePathString(), + text = file.readText(StandardCharsets.UTF_8), + ruleSets = setOf(diktatRuleSetProvider().toKtLint()), + userData = emptyMap(), + cb = callback, + script = false, // internal API of KtLint + editorConfigPath = null, + debug = false, // we do not use it + editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride, + isInvokedFromCli = false + ) +} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt new file mode 100644 index 0000000000..af6b3e8331 --- /dev/null +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -0,0 +1,46 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.api.DiktatReporterFactory +import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap +import com.pinterest.ktlint.core.ReporterProvider +import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider +import com.pinterest.ktlint.reporter.html.HtmlReporterProvider +import com.pinterest.ktlint.reporter.json.JsonReporterProvider +import com.pinterest.ktlint.reporter.plain.PlainReporterProvider +import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider +import java.io.OutputStream +import java.io.PrintStream +import java.nio.file.Path +import kotlin.io.path.pathString + +/** + * A factory to create [DiktatReporter] using `KtLint` + */ +class DiktatReporterFactoryImpl : DiktatReporterFactory { + /** + * All [ReporterProvider] which __KtLint__ provides + */ + private val reporterProviders = setOf( + PlainReporterProvider(), + JsonReporterProvider(), + SarifReporterProvider(), + CheckStyleReporterProvider(), + HtmlReporterProvider(), + ) + .associateBy { it.id } + + override fun invoke( + id: String, + outputStream: OutputStream, + extraProperties: Map, + sourceRootDir: Path, + ): DiktatReporter { + val reporterProvider = reporterProviders[id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") + if (reporterProvider is SarifReporterProvider) { + System.setProperty("user.home", sourceRootDir.pathString) + } + val printStream = (outputStream as? PrintStream) ?: PrintStream(outputStream) + return reporterProvider.get(printStream, extraProperties).wrap(sourceRootDir) + } +} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt new file mode 100644 index 0000000000..3469be1231 --- /dev/null +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt @@ -0,0 +1,34 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatError +import org.cqfn.diktat.api.DiktatReporter +import com.pinterest.ktlint.core.Reporter +import java.nio.file.Path + +/** + * [DiktatReporter] using __KtLint__ + */ +class DiktatReporterImpl( + private val ktLintReporter: Reporter, + private val sourceRootDir: Path, +) : DiktatReporter { + override fun beforeAll(): Unit = ktLintReporter.beforeAll() + override fun before(file: Path): Unit = ktLintReporter.before(file.relativePathStringTo(sourceRootDir)) + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = ktLintReporter.onLintError(file.relativePathStringTo(sourceRootDir), error.unwrap(), isCorrected) + override fun after(file: Path): Unit = ktLintReporter.after(file.relativePathStringTo(sourceRootDir)) + override fun afterAll(): Unit = ktLintReporter.beforeAll() + + companion object { + /** + * @param sourceRootDir + * @return [DiktatReporter] which wraps __KtLint__'s [Reporter] + */ + fun Reporter.wrap(sourceRootDir: Path) = DiktatReporterImpl(this, sourceRootDir) + + /** + * @return __KtLint__'s [Reporter] + */ + internal fun DiktatReporter.unwrap(): Reporter = (this as? DiktatReporterImpl)?.ktLintReporter + ?: error("Unsupported wrapper of ${DiktatReporter::class.java.simpleName}: ${this::class.java.canonicalName}") + } +} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt new file mode 100644 index 0000000000..f655d6549d --- /dev/null +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt @@ -0,0 +1,15 @@ +/** + * This file contains util methods for __KtLint__ + */ + +package org.cqfn.diktat.ktlint + +import java.nio.file.Path +import kotlin.io.path.invariantSeparatorsPathString +import kotlin.io.path.relativeTo + +/** + * @param sourceRootDir + * @return relative path to [sourceRootDir] as [String] + */ +fun Path.relativePathStringTo(sourceRootDir: Path): String = relativeTo(sourceRootDir).invariantSeparatorsPathString diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt index b9352d2a57..300c3995dd 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt @@ -5,6 +5,8 @@ package org.cqfn.diktat.ktlint import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap import org.cqfn.diktat.ruleset.utils.LintErrorCallback /** diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorReporter.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorReporter.kt deleted file mode 100644 index 2bd5e13828..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorReporter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.cqfn.diktat.ktlint - -import com.pinterest.ktlint.core.LintError -import com.pinterest.ktlint.core.Reporter -import java.util.concurrent.atomic.AtomicInteger - -/** - * An implementation of [Reporter] which counts [LintError]s - */ -class LintErrorReporter : Reporter { - private val errorCounter: AtomicInteger = AtomicInteger() - - override fun onLintError( - file: String, - err: LintError, - corrected: Boolean - ) { - errorCounter.incrementAndGet() - } - - /** - * @return true if there is a reported [LintError], otherwise -- false. - */ - fun isNotEmpty(): Boolean = errorCounter.get() != 0 - - /** - * @return count of reported [LintError] - */ - fun errorCount(): Int = errorCounter.get() -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt new file mode 100644 index 0000000000..c8dc1cefe0 --- /dev/null +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt @@ -0,0 +1,91 @@ +/** + * File contains util methods to create ktlint's [Reporter] + */ + +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatMode +import com.pinterest.ktlint.core.Reporter +import com.pinterest.ktlint.core.ReporterProvider +import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider +import com.pinterest.ktlint.reporter.html.HtmlReporterProvider +import com.pinterest.ktlint.reporter.json.JsonReporterProvider +import com.pinterest.ktlint.reporter.plain.internal.Color +import com.pinterest.ktlint.reporter.plain.PlainReporterProvider +import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider +import java.io.PrintStream +import java.nio.file.Paths +import kotlin.io.path.createDirectories +import kotlin.io.path.outputStream + +/** + * supported color names in __KtLint__, taken from [Color] + */ +val colorNamesForPlainReporter = Color.values().map { it.name } + +/** + * A default [ReporterProvider] for [PlainReporterProvider] + */ +val plainReporterProvider = PlainReporterProvider() + +/** + * All [ReporterProvider] which __KtLint__ provides + */ +val reporterProviders = setOf( + plainReporterProvider, + JsonReporterProvider(), + SarifReporterProvider(), + CheckStyleReporterProvider(), + HtmlReporterProvider(), +) + .associateBy { it.id } + +/** + * @return true if receiver is [PlainReporterProvider] + */ +internal fun ReporterProvider<*>.isPlain(): Boolean = id == plainReporterProvider.id + +/** + * @param reporterProviderId + * @param output + * @param colorNameInPlain + * @param groupByFileInPlain + * @param mode + * @return a configured [Reporter] + */ +fun buildReporter( + reporterProviderId: String, + output: String?, + colorNameInPlain: String?, + groupByFileInPlain: Boolean, + mode: DiktatMode, +): Reporter { + val reporterProvider = reporterProviders.getValue(reporterProviderId) + return reporterProvider.get( + out = output + ?.let { Paths.get(it) } + ?.also { it.parent.createDirectories() } + ?.outputStream() + ?.let { PrintStream(it) } + ?: System.out, + opt = buildMap { + colorNameInPlain?.let { + require(reporterProvider.isPlain()) { + "colorization is applicable only for plain reporter" + } + put("color", true) + put("color_name", it) + } ?: run { + put("color", false) + put("color_name", Color.DARK_GRAY.name) + } + put("format", (mode == DiktatMode.FIX)) + if (groupByFileInPlain) { + require(reporterProvider.isPlain()) { + "groupByFile is applicable only for plain reporter" + } + put("group_by_file", true) + } + }.mapValues { it.value.toString() }, + ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 61106ad854..324facc6ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ jupiter-itf-extension = "0.12.0" mockito-all = "1.10.19" # executable jar -log4j = "2.20.0" +log4j2 = "2.20.0" kotlinx-cli = "0.3.5" # copied from save-cloud From a96d996200d82967473995f0607068b4b066554a Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Fri, 31 Mar 2023 17:39:37 +0300 Subject: [PATCH 03/34] fixed import in diktat-runner-cli --- .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 63 ++++++------- .../org/cqfn/diktat/cli/DiktatProperties.kt | 85 ++++++++++++++++- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 6 +- .../ktlint/DiktatBaselineFactoryImpl.kt | 5 + .../cqfn/diktat/ktlint/DiktatReporterImpl.kt | 3 +- .../org/cqfn/diktat/ktlint/ReporterUtil.kt | 91 ------------------- 6 files changed, 118 insertions(+), 135 deletions(-) delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt index 5c87f8dd0d..3dec0192b7 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -4,24 +4,24 @@ package org.cqfn.diktat -import org.cqfn.diktat.api.DiktatError import org.cqfn.diktat.api.DiktatMode +import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.cli.DiktatProperties import org.cqfn.diktat.common.utils.loggerWithKtlintConfig -import org.cqfn.diktat.ktlint.unwrap +import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript import org.cqfn.diktat.util.tryToPathIfExists import org.cqfn.diktat.util.walkByGlob import mu.KotlinLogging +import java.nio.charset.StandardCharsets +import java.nio.file.Path import java.nio.file.Paths -import kotlin.io.path.absolutePathString +import kotlin.io.path.readText import kotlin.io.path.writeText @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") private val log = KotlinLogging.loggerWithKtlintConfig {} -typealias DiktatErrorWithCorrectionInfo = Pair - @Suppress( "LongMethod", "TOO_LONG_FUNCTION" @@ -33,17 +33,15 @@ fun main(args: Array) { log.debug { "Loading diktatRuleSet using config ${properties.config}" } - val diktatProcessor = DiktatProcessor.builder() - .diktatRuleSetProvider(properties.config) - .build() - val reporter = properties.reporter() - reporter.beforeAll() + val diktatProcessor = DiktatProcessorFactoryImpl() + .create(properties.config) + val currentFolder = Paths.get(".") + val reporter = properties.reporter(currentFolder) log.debug { "Resolving files by patterns: ${properties.patterns}" } - val currentFolder = Paths.get(".") - properties.patterns + val files = properties.patterns .asSequence() .flatMap { pattern -> pattern.tryToPathIfExists()?.let { sequenceOf(it) } @@ -52,35 +50,26 @@ fun main(args: Array) { .filter { file -> file.isKotlinCodeOrScript() } .distinct() .map { it.normalize() } - .map { file -> + + val loggingListener = object : DiktatProcessorListener.Companion.Empty() { + override fun before(file: Path) { log.debug { "Start processing the file: $file" } - val result: MutableList = mutableListOf() - DiktatProcessCommand.builder() - .processor(diktatProcessor) - .file(file) - .callback { error, isCorrected -> - result.add(error to isCorrected) - } - .build() - .let { command -> - when (properties.mode) { - DiktatMode.CHECK -> command.check() - DiktatMode.FIX -> { - val formattedFileContent = command.fix() - file.writeText(formattedFileContent, Charsets.UTF_8) - } - } - } - file to result } - .forEach { (file, result) -> - reporter.before(file.absolutePathString()) - result.forEach { (error, isCorrected) -> - reporter.onLintError(file.absolutePathString(), error.unwrap(), isCorrected) + } + when (properties.mode) { + DiktatMode.CHECK -> diktatProcessor.checkAll( + listener = DiktatProcessorListener(loggingListener, reporter), + files = files, + ) + DiktatMode.FIX -> diktatProcessor.fixAll( + listener = DiktatProcessorListener(loggingListener, reporter), + files = files, + ) { file, formatterText -> + if (file.readText(StandardCharsets.UTF_8) != formatterText) { + file.writeText(formatterText, Charsets.UTF_8) } - reporter.after(file.absolutePathString()) } - reporter.afterAll() + } } diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 8f289f702e..02fe578c78 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -1,15 +1,28 @@ package org.cqfn.diktat.cli import org.cqfn.diktat.api.DiktatMode +import org.cqfn.diktat.api.DiktatReporter import org.cqfn.diktat.common.config.rules.DIKTAT import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF -import org.cqfn.diktat.ktlint.buildReporter +import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap import org.cqfn.diktat.util.colorName import org.cqfn.diktat.util.reporterProviderId import com.pinterest.ktlint.core.Reporter +import com.pinterest.ktlint.core.ReporterProvider +import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider +import com.pinterest.ktlint.reporter.html.HtmlReporterProvider +import com.pinterest.ktlint.reporter.json.JsonReporterProvider +import com.pinterest.ktlint.reporter.plain.PlainReporterProvider +import com.pinterest.ktlint.reporter.plain.internal.Color +import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LoggerContext import org.slf4j.event.Level +import java.io.PrintStream +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.createDirectories +import kotlin.io.path.outputStream import kotlin.system.exitProcess import kotlinx.cli.ArgParser import kotlinx.cli.ArgType @@ -34,11 +47,12 @@ data class DiktatProperties( val patterns: List, ) { /** + * @param sourceRootDir * @return a configured [Reporter] */ - fun reporter(): Reporter = buildReporter( + fun reporter(sourceRootDir: Path): DiktatReporter = buildReporter( reporterProviderId, output, colorNameInPlain, groupByFileInPlain, mode - ) + ).wrap(sourceRootDir) /** * Configure logger level using [logLevel] @@ -161,5 +175,70 @@ data class DiktatProperties( .getResource(resourceName) ?.readText() ?: error("Resource $resourceName not found") + + private fun buildReporter( + reporterProviderId: String, + output: String?, + colorNameInPlain: String?, + groupByFileInPlain: Boolean, + mode: DiktatMode, + ): Reporter { + val reporterProvider = reporterProviders.getValue(reporterProviderId) + return reporterProvider.get( + out = output + ?.let { Paths.get(it) } + ?.also { it.parent.createDirectories() } + ?.outputStream() + ?.let { PrintStream(it) } + ?: System.out, + opt = buildMap { + colorNameInPlain?.let { + require(reporterProvider.isPlain()) { + "colorization is applicable only for plain reporter" + } + put("color", true) + put("color_name", it) + } ?: run { + put("color", false) + put("color_name", Color.DARK_GRAY.name) + } + put("format", (mode == DiktatMode.FIX)) + if (groupByFileInPlain) { + require(reporterProvider.isPlain()) { + "groupByFile is applicable only for plain reporter" + } + put("group_by_file", true) + } + }.mapValues { it.value.toString() }, + ) + } + + + /** + * supported color names in __KtLint__, taken from [Color] + */ + val colorNamesForPlainReporter = Color.values().map { it.name } + + /** + * A default [ReporterProvider] for [PlainReporterProvider] + */ + val plainReporterProvider = PlainReporterProvider() + + /** + * All [ReporterProvider] which __KtLint__ provides + */ + val reporterProviders = setOf( + plainReporterProvider, + JsonReporterProvider(), + SarifReporterProvider(), + CheckStyleReporterProvider(), + HtmlReporterProvider(), + ) + .associateBy { it.id } + + /** + * @return true if receiver is [PlainReporterProvider] + */ + internal fun ReporterProvider<*>.isPlain(): Boolean = id == plainReporterProvider.id } } diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index d1da8ec262..1ade0fecb9 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -4,9 +4,9 @@ package org.cqfn.diktat.util -import org.cqfn.diktat.ktlint.colorNamesForPlainReporter -import org.cqfn.diktat.ktlint.plainReporterProvider -import org.cqfn.diktat.ktlint.reporterProviders +import org.cqfn.diktat.cli.DiktatProperties.Companion.colorNamesForPlainReporter +import org.cqfn.diktat.cli.DiktatProperties.Companion.plainReporterProvider +import org.cqfn.diktat.cli.DiktatProperties.Companion.reporterProviders import com.pinterest.ktlint.core.ReporterProvider import kotlinx.cli.ArgParser import kotlinx.cli.ArgType diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt index ecb24b080f..ae04bbb8c9 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt @@ -4,6 +4,7 @@ import org.cqfn.diktat.api.DiktatBaseline import org.cqfn.diktat.api.DiktatBaselineFactory import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap import com.pinterest.ktlint.core.internal.loadBaseline import com.pinterest.ktlint.reporter.baseline.BaselineReporter @@ -12,6 +13,10 @@ import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.outputStream +/** + * A factory to create or generate [DiktatBaseline] using `KtLint` + */ +@Suppress("DEPRECATION") class DiktatBaselineFactoryImpl : DiktatBaselineFactory { override fun tryToLoad(baselineFile: Path, sourceRootDir: Path): DiktatBaseline? { return loadBaseline(baselineFile.absolutePathString()) diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt index 3469be1231..a6e1b437d0 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt @@ -2,6 +2,7 @@ package org.cqfn.diktat.ktlint import org.cqfn.diktat.api.DiktatError import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap import com.pinterest.ktlint.core.Reporter import java.nio.file.Path @@ -23,7 +24,7 @@ class DiktatReporterImpl( * @param sourceRootDir * @return [DiktatReporter] which wraps __KtLint__'s [Reporter] */ - fun Reporter.wrap(sourceRootDir: Path) = DiktatReporterImpl(this, sourceRootDir) + fun Reporter.wrap(sourceRootDir: Path): DiktatReporter = DiktatReporterImpl(this, sourceRootDir) /** * @return __KtLint__'s [Reporter] diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt deleted file mode 100644 index c8dc1cefe0..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/ReporterUtil.kt +++ /dev/null @@ -1,91 +0,0 @@ -/** - * File contains util methods to create ktlint's [Reporter] - */ - -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatMode -import com.pinterest.ktlint.core.Reporter -import com.pinterest.ktlint.core.ReporterProvider -import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider -import com.pinterest.ktlint.reporter.html.HtmlReporterProvider -import com.pinterest.ktlint.reporter.json.JsonReporterProvider -import com.pinterest.ktlint.reporter.plain.internal.Color -import com.pinterest.ktlint.reporter.plain.PlainReporterProvider -import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider -import java.io.PrintStream -import java.nio.file.Paths -import kotlin.io.path.createDirectories -import kotlin.io.path.outputStream - -/** - * supported color names in __KtLint__, taken from [Color] - */ -val colorNamesForPlainReporter = Color.values().map { it.name } - -/** - * A default [ReporterProvider] for [PlainReporterProvider] - */ -val plainReporterProvider = PlainReporterProvider() - -/** - * All [ReporterProvider] which __KtLint__ provides - */ -val reporterProviders = setOf( - plainReporterProvider, - JsonReporterProvider(), - SarifReporterProvider(), - CheckStyleReporterProvider(), - HtmlReporterProvider(), -) - .associateBy { it.id } - -/** - * @return true if receiver is [PlainReporterProvider] - */ -internal fun ReporterProvider<*>.isPlain(): Boolean = id == plainReporterProvider.id - -/** - * @param reporterProviderId - * @param output - * @param colorNameInPlain - * @param groupByFileInPlain - * @param mode - * @return a configured [Reporter] - */ -fun buildReporter( - reporterProviderId: String, - output: String?, - colorNameInPlain: String?, - groupByFileInPlain: Boolean, - mode: DiktatMode, -): Reporter { - val reporterProvider = reporterProviders.getValue(reporterProviderId) - return reporterProvider.get( - out = output - ?.let { Paths.get(it) } - ?.also { it.parent.createDirectories() } - ?.outputStream() - ?.let { PrintStream(it) } - ?: System.out, - opt = buildMap { - colorNameInPlain?.let { - require(reporterProvider.isPlain()) { - "colorization is applicable only for plain reporter" - } - put("color", true) - put("color_name", it) - } ?: run { - put("color", false) - put("color_name", Color.DARK_GRAY.name) - } - put("format", (mode == DiktatMode.FIX)) - if (groupByFileInPlain) { - require(reporterProvider.isPlain()) { - "groupByFile is applicable only for plain reporter" - } - put("group_by_file", true) - } - }.mapValues { it.value.toString() }, - ) -} From b18848127b03d65dc779fdd0098aa60bc81cdc3b Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Fri, 31 Mar 2023 17:45:39 +0300 Subject: [PATCH 04/34] fixed reporter creation --- .../org/cqfn/diktat/cli/DiktatProperties.kt | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 02fe578c78..28a03098a2 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.api.DiktatMode import org.cqfn.diktat.api.DiktatReporter import org.cqfn.diktat.common.config.rules.DIKTAT import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF -import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap +import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl import org.cqfn.diktat.util.colorName import org.cqfn.diktat.util.reporterProviderId import com.pinterest.ktlint.core.Reporter @@ -51,8 +51,8 @@ data class DiktatProperties( * @return a configured [Reporter] */ fun reporter(sourceRootDir: Path): DiktatReporter = buildReporter( - reporterProviderId, output, colorNameInPlain, groupByFileInPlain, mode - ).wrap(sourceRootDir) + reporterProviderId, output, colorNameInPlain, groupByFileInPlain, sourceRootDir + ) /** * Configure logger level using [logLevel] @@ -181,19 +181,20 @@ data class DiktatProperties( output: String?, colorNameInPlain: String?, groupByFileInPlain: Boolean, - mode: DiktatMode, - ): Reporter { - val reporterProvider = reporterProviders.getValue(reporterProviderId) - return reporterProvider.get( - out = output - ?.let { Paths.get(it) } - ?.also { it.parent.createDirectories() } - ?.outputStream() - ?.let { PrintStream(it) } - ?: System.out, - opt = buildMap { + sourceRootDir: Path, + ): DiktatReporter { + val outputStream = output + ?.let { Paths.get(it) } + ?.also { it.parent.createDirectories() } + ?.outputStream() + ?.let { PrintStream(it) } + ?: System.out + return DiktatReporterFactoryImpl().invoke( + reporterProviderId, + outputStream, + buildMap { colorNameInPlain?.let { - require(reporterProvider.isPlain()) { + require(reporterProviderId == "plain") { "colorization is applicable only for plain reporter" } put("color", true) @@ -202,14 +203,14 @@ data class DiktatProperties( put("color", false) put("color_name", Color.DARK_GRAY.name) } - put("format", (mode == DiktatMode.FIX)) if (groupByFileInPlain) { - require(reporterProvider.isPlain()) { + require(reporterProviderId == "plain") { "groupByFile is applicable only for plain reporter" } put("group_by_file", true) } }.mapValues { it.value.toString() }, + sourceRootDir, ) } From 3ff383a0af0db0c1d8d8692e29ca41e49bcf51ae Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Fri, 31 Mar 2023 17:58:49 +0300 Subject: [PATCH 05/34] WIP --- .../kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt | 9 ++++++--- .../org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt | 3 +-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt index 93e48e275a..b264336d77 100644 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt @@ -8,18 +8,21 @@ typealias DiktatReporter = DiktatProcessorListener /** * A factory to create [DiktatReporter] */ -fun interface DiktatReporterFactory : Function4, Path, DiktatReporter> { +interface DiktatReporterFactory : Function3 { + /** + * Set of supported IDs + */ + val ids: Set + /** * @param id ID of [DiktatReporter] * @param outputStream - * @param extraProperties extra properties which can be required to create [DiktatReporter] * @param sourceRootDir a dir to detect relative path for processing files * @return created [DiktatReporter] */ override operator fun invoke( id: String, outputStream: OutputStream, - extraProperties: Map, sourceRootDir: Path, ): DiktatReporter } diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt index af6b3e8331..0955f23073 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -33,7 +33,6 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { override fun invoke( id: String, outputStream: OutputStream, - extraProperties: Map, sourceRootDir: Path, ): DiktatReporter { val reporterProvider = reporterProviders[id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") @@ -41,6 +40,6 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { System.setProperty("user.home", sourceRootDir.pathString) } val printStream = (outputStream as? PrintStream) ?: PrintStream(outputStream) - return reporterProvider.get(printStream, extraProperties).wrap(sourceRootDir) + return reporterProvider.get(printStream, emptyMap()).wrap(sourceRootDir) } } From 57be97449db97a6ac0ff7501f3b246c42a102f83 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 3 Apr 2023 13:00:42 +0300 Subject: [PATCH 06/34] extracted plain reporter to a dedicated function in factory --- .../plugin/gradle/tasks/DiktatTaskBase.kt | 12 +- .../diktat/plugin/maven/DiktatBaseMojo.kt | 2 +- .../cqfn/diktat/api/DiktatReporterFactory.kt | 24 ++++ .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 9 +- .../org/cqfn/diktat/cli/DiktatProperties.kt | 125 +++++++++--------- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 42 ------ .../ktlint/DiktatReporterFactoryImpl.kt | 42 +++++- 7 files changed, 137 insertions(+), 119 deletions(-) delete mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt index 058076f99c..15e25dafa5 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt @@ -210,10 +210,14 @@ abstract class DiktatTaskBase( private fun createReporter(): DiktatReporter { val reporterType = project.getReporterType(extension) - val (outputStream, closeListener) = project.getOutputFile(extension)?.outputStream()?.let { - it to it.closeAfterAllAsProcessorListener() - } ?: (System.out to DiktatProcessorListener.empty) - val actualReporter = reporterFactory(reporterType, outputStream ?: System.`out`, emptyMap(), project.rootDir.toPath()) + val (outputStream, closeListener) = project.getOutputFile(extension) + ?.outputStream() + ?.let { + it to it.closeAfterAllAsProcessorListener() + } ?: run { + System.`out` to DiktatProcessorListener.empty + } + val actualReporter = reporterFactory(reporterType, outputStream, project.rootDir.toPath()) return DiktatProcessorListener(actualReporter, closeListener) } } 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 ef50312eb2..222083cd94 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 @@ -172,7 +172,7 @@ abstract class DiktatBaseMojo : AbstractMojo() { ?: run { System.`out` to DiktatProcessorListener.empty } - val actualReporter = reporterFactory(reporterType, outputStream, emptyMap(), sourceRootDir) + val actualReporter = reporterFactory(reporterType, outputStream, sourceRootDir) return DiktatProcessorListener(actualReporter, closeListener) } diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt index b264336d77..55f054f3ed 100644 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt @@ -14,6 +14,16 @@ interface DiktatReporterFactory : Function3 + /** + * ID of [DiktatReporter] for plain output + */ + val plainId: String + + /** + * Names of color for plain output + */ + val colorNamesInPlain: Set + /** * @param id ID of [DiktatReporter] * @param outputStream @@ -25,4 +35,18 @@ interface DiktatReporterFactory : Function3) { - val properties = DiktatProperties.parse(args) + val diktatReporterFactory = DiktatReporterFactoryImpl() + val properties = DiktatProperties.parse(diktatReporterFactory, args) properties.configureLogger() log.debug { @@ -36,7 +37,7 @@ fun main(args: Array) { val diktatProcessor = DiktatProcessorFactoryImpl() .create(properties.config) val currentFolder = Paths.get(".") - val reporter = properties.reporter(currentFolder) + val reporter = properties.reporter(diktatReporterFactory, currentFolder) log.debug { "Resolving files by patterns: ${properties.patterns}" diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 28a03098a2..29753bfe02 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -2,19 +2,11 @@ package org.cqfn.diktat.cli import org.cqfn.diktat.api.DiktatMode import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.api.DiktatReporterFactory import org.cqfn.diktat.common.config.rules.DIKTAT import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF -import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl -import org.cqfn.diktat.util.colorName -import org.cqfn.diktat.util.reporterProviderId import com.pinterest.ktlint.core.Reporter import com.pinterest.ktlint.core.ReporterProvider -import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider -import com.pinterest.ktlint.reporter.html.HtmlReporterProvider -import com.pinterest.ktlint.reporter.json.JsonReporterProvider -import com.pinterest.ktlint.reporter.plain.PlainReporterProvider -import com.pinterest.ktlint.reporter.plain.internal.Color -import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LoggerContext import org.slf4j.event.Level @@ -47,11 +39,18 @@ data class DiktatProperties( val patterns: List, ) { /** + * @param diktatReporterFactory * @param sourceRootDir * @return a configured [Reporter] */ - fun reporter(sourceRootDir: Path): DiktatReporter = buildReporter( - reporterProviderId, output, colorNameInPlain, groupByFileInPlain, sourceRootDir + fun reporter( + diktatReporterFactory: DiktatReporterFactory, + sourceRootDir: Path, + ): DiktatReporter = buildReporter( + diktatReporterFactory, reporterProviderId, + output, + colorNameInPlain, groupByFileInPlain, + sourceRootDir, ) /** @@ -75,6 +74,7 @@ data class DiktatProperties( companion object { /** + * @param diktatReporterFactory * @param args cli arguments * @return parsed [DiktatProperties] */ @@ -82,7 +82,10 @@ data class DiktatProperties( "LongMethod", "TOO_LONG_FUNCTION" ) - fun parse(args: Array): DiktatProperties { + fun parse( + diktatReporterFactory: DiktatReporterFactory, + args: Array, + ): DiktatProperties { val parser = ArgParser(DIKTAT) val config: String by parser.option( type = ArgType.String, @@ -96,7 +99,7 @@ data class DiktatProperties( shortName = "m", description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." ).default(DiktatMode.CHECK) - val reporterProviderId: String by parser.reporterProviderId() + val reporterProviderId: String by parser.reporterProviderId(diktatReporterFactory) val output: String? by parser.option( type = ArgType.String, fullName = "output", @@ -109,7 +112,7 @@ data class DiktatProperties( shortName = null, description = "A flag for plain reporter" ).default(false) - val colorName: String? by parser.colorName() + val colorName: String? by parser.colorName(diktatReporterFactory) val logLevel: Level by parser.option( type = ArgType.Choice(), fullName = "log-level", @@ -149,6 +152,37 @@ data class DiktatProperties( ) } + /** + * @param diktatReporterFactory + * @return a single [ReporterProvider] as parsed cli arg + */ + private fun ArgParser.reporterProviderId(diktatReporterFactory: DiktatReporterFactory) = option( + type = ArgType.Choice( + choices = diktatReporterFactory.ids.toList(), + toVariant = { it }, + variantToString = { it }, + ), + fullName = "reporter", + shortName = "r", + description = "The reporter to use" + ) + .default(diktatReporterFactory.plainId) + + /** + * @param diktatReporterFactory + * @return a single and optional color name as parsed cli args + */ + private fun ArgParser.colorName(diktatReporterFactory: DiktatReporterFactory) = this.option( + type = ArgType.Choice( + choices = diktatReporterFactory.colorNamesInPlain.toList(), + toVariant = { it }, + variantToString = { it }, + ), + fullName = "plain-color", + shortName = null, + description = "Colorize the output.", + ) + private fun ArgParser.addOptionAndShowResourceWithExit( fullName: String, shortName: String?, @@ -177,6 +211,7 @@ data class DiktatProperties( ?: error("Resource $resourceName not found") private fun buildReporter( + diktatReporterFactory: DiktatReporterFactory, reporterProviderId: String, output: String?, colorNameInPlain: String?, @@ -189,57 +224,17 @@ data class DiktatProperties( ?.outputStream() ?.let { PrintStream(it) } ?: System.out - return DiktatReporterFactoryImpl().invoke( - reporterProviderId, - outputStream, - buildMap { - colorNameInPlain?.let { - require(reporterProviderId == "plain") { - "colorization is applicable only for plain reporter" - } - put("color", true) - put("color_name", it) - } ?: run { - put("color", false) - put("color_name", Color.DARK_GRAY.name) - } - if (groupByFileInPlain) { - require(reporterProviderId == "plain") { - "groupByFile is applicable only for plain reporter" - } - put("group_by_file", true) - } - }.mapValues { it.value.toString() }, - sourceRootDir, - ) + return if (reporterProviderId == diktatReporterFactory.plainId) { + diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) + } else { + require(colorNameInPlain == null) { + "colorization is applicable only for plain reporter" + } + require(!groupByFileInPlain) { + "groupByFile is applicable only for plain reporter" + } + diktatReporterFactory.invoke(reporterProviderId, outputStream, sourceRootDir) + } } - - - /** - * supported color names in __KtLint__, taken from [Color] - */ - val colorNamesForPlainReporter = Color.values().map { it.name } - - /** - * A default [ReporterProvider] for [PlainReporterProvider] - */ - val plainReporterProvider = PlainReporterProvider() - - /** - * All [ReporterProvider] which __KtLint__ provides - */ - val reporterProviders = setOf( - plainReporterProvider, - JsonReporterProvider(), - SarifReporterProvider(), - CheckStyleReporterProvider(), - HtmlReporterProvider(), - ) - .associateBy { it.id } - - /** - * @return true if receiver is [PlainReporterProvider] - */ - internal fun ReporterProvider<*>.isPlain(): Boolean = id == plainReporterProvider.id } } diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt deleted file mode 100644 index 1ade0fecb9..0000000000 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ /dev/null @@ -1,42 +0,0 @@ -/** - * This class contains util methods to operate with kotlinx.cli.* - */ - -package org.cqfn.diktat.util - -import org.cqfn.diktat.cli.DiktatProperties.Companion.colorNamesForPlainReporter -import org.cqfn.diktat.cli.DiktatProperties.Companion.plainReporterProvider -import org.cqfn.diktat.cli.DiktatProperties.Companion.reporterProviders -import com.pinterest.ktlint.core.ReporterProvider -import kotlinx.cli.ArgParser -import kotlinx.cli.ArgType -import kotlinx.cli.default - -/** - * @return a single [ReporterProvider] as parsed cli arg - */ -internal fun ArgParser.reporterProviderId() = option( - type = ArgType.Choice( - choices = reporterProviders.keys.toList(), - toVariant = { it }, - variantToString = { it }, - ), - fullName = "reporter", - shortName = "r", - description = "The reporter to use" -) - .default(plainReporterProvider.id) - -/** - * @return a single and optional color name as parsed cli args - */ -internal fun ArgParser.colorName() = this.option( - type = ArgType.Choice( - choices = colorNamesForPlainReporter, - toVariant = { it }, - variantToString = { it }, - ), - fullName = "plain-color", - shortName = null, - description = "Colorize the output.", -) diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt index 0955f23073..8fbb804a43 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -8,6 +8,7 @@ import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider import com.pinterest.ktlint.reporter.html.HtmlReporterProvider import com.pinterest.ktlint.reporter.json.JsonReporterProvider import com.pinterest.ktlint.reporter.plain.PlainReporterProvider +import com.pinterest.ktlint.reporter.plain.internal.Color import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider import java.io.OutputStream import java.io.PrintStream @@ -18,11 +19,13 @@ import kotlin.io.path.pathString * A factory to create [DiktatReporter] using `KtLint` */ class DiktatReporterFactoryImpl : DiktatReporterFactory { + private val plainReporterProvider = PlainReporterProvider() + /** * All [ReporterProvider] which __KtLint__ provides */ private val reporterProviders = setOf( - PlainReporterProvider(), + plainReporterProvider, JsonReporterProvider(), SarifReporterProvider(), CheckStyleReporterProvider(), @@ -30,6 +33,15 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { ) .associateBy { it.id } + override val ids: Set + get() = reporterProviders.keys + + override val plainId: String + get() = plainReporterProvider.id + + override val colorNamesInPlain: Set + get() = Color.values().map { it.name }.toSet() + override fun invoke( id: String, outputStream: OutputStream, @@ -39,7 +51,31 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { if (reporterProvider is SarifReporterProvider) { System.setProperty("user.home", sourceRootDir.pathString) } - val printStream = (outputStream as? PrintStream) ?: PrintStream(outputStream) - return reporterProvider.get(printStream, emptyMap()).wrap(sourceRootDir) + val opt = if (reporterProvider is PlainReporterProvider) { + mapOf("color" to "DARK_GRAY") + } else { + emptyMap() + } + return reporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) + } + + override fun createPlain(outputStream: OutputStream, sourceRootDir: Path, colorName: String?, groupByFile: Boolean): DiktatReporter { + val opt = buildMap { + colorName?.let { + put("color", true) + put("color_name", it) + } ?: run { + put("color", false) + put("color_name", Color.DARK_GRAY) + } + if (groupByFile) { + put("group_by_file", true) + } + }.mapValues { it.value.toString() } + return plainReporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) + } + + companion object { + private fun OutputStream.asPrintStream(): PrintStream = (this as? PrintStream) ?: PrintStream(this) } } From fee0e511c0884c5fe8c5c39817370b789bd7e9da Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 3 Apr 2023 16:14:11 +0300 Subject: [PATCH 07/34] moved all ktlint to diktat-runner-ktlint-engine --- .../config/reader/ApplicationProperties.kt | 3 +- .../config/reader/JsonResourceConfigReader.kt | 3 +- .../common/config/rules/RulesConfigReader.kt | 3 +- diktat-gradle-plugin/build.gradle.kts | 7 +- .../plugin/gradle/tasks/DiktatTaskBase.kt | 11 ++- diktat-maven-plugin/build.gradle.kts | 7 +- .../diktat/plugin/maven/DiktatBaseMojo.kt | 7 +- diktat-rules/build.gradle.kts | 2 + .../ktlint/KtLintRuleSetProviderWrapper.kt | 22 ----- .../cqfn/diktat/ruleset/rules/DiktatRule.kt | 17 ++-- .../ruleset/rules/DiktatRuleSetFactoryImpl.kt | 13 +++ .../ruleset/rules/DiktatRuleSetProvider.kt | 1 + .../cqfn/diktat/ruleset/utils/KtlintUtils.kt | 73 ----------------- .../smoke/RulesConfigValidationTest.kt | 7 +- .../diktat/util/DiktatRuleSetProvider4Test.kt | 34 +++----- .../org/cqfn/diktat/util/FixTestBase.kt | 11 ++- diktat-ruleset/build.gradle.kts | 1 + .../ruleset/rules/DiktatRuleSetProviderSpi.kt | 4 +- .../com.pinterest.ktlint.core.RuleSetProvider | 0 .../diktat/ruleset/smoke/DiktatSmokeTest.kt | 11 +-- .../ruleset/smoke/DiktatSmokeTestBase.kt | 1 + .../diktat-runner-api/build.gradle.kts | 4 +- .../org/cqfn/diktat/DiktatProcessorFactory.kt | 26 ++---- .../org/cqfn/diktat/api/DiktatErrorEmitter.kt | 20 +++++ .../org/cqfn/diktat/api/DiktatLogLevel.kt | 10 --- .../kotlin/org/cqfn/diktat/api/DiktatRule.kt | 26 ++++++ .../org/cqfn/diktat/api}/DiktatRuleSet.kt | 2 +- .../cqfn/diktat/api/DiktatRuleSetFactory.kt | 26 ++++++ .../diktat-runner-cli/build.gradle.kts | 2 + .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 2 +- .../kotlin/org/cqfn/diktat/cli}/DiktatMode.kt | 2 +- .../org/cqfn/diktat/cli/DiktatProperties.kt | 53 +++++------- .../build.gradle.kts | 7 +- .../cqfn/diktat/common/utils/LoggingUtils.kt | 0 .../org/cqfn/diktat/ktlint/DiktatErrorImpl.kt | 2 +- .../ktlint/DiktatProcessorFactoryImpl.kt | 15 ++-- .../diktat/ktlint/KtLintRuleSetWrapper.kt | 4 +- .../cqfn/diktat/ktlint/KtLintRuleWrapper.kt | 9 ++- .../org/cqfn/diktat/ktlint/KtLintUtils.kt | 81 +++++++++++++++++++ .../diktat/ktlint/LintErrorCallbackWrapper.kt | 24 ------ .../diktat/ktlint/KtLintRuleSetWrapperTest.kt | 18 +++-- .../framework/config/TestArgumentsReader.kt | 5 +- 42 files changed, 291 insertions(+), 285 deletions(-) delete mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetProviderWrapper.kt create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt delete mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KtlintUtils.kt rename {diktat-rules => diktat-ruleset}/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt (78%) rename {diktat-rules => diktat-ruleset}/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider (100%) create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatLogLevel.kt create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt rename {diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules => diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api}/DiktatRuleSet.kt (79%) create mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt rename diktat-runner/{diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api => diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli}/DiktatMode.kt (88%) rename {diktat-common => diktat-runner/diktat-runner-ktlint-engine}/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt (100%) rename {diktat-rules => diktat-runner/diktat-runner-ktlint-engine}/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt (92%) rename {diktat-rules => diktat-runner/diktat-runner-ktlint-engine}/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt (83%) delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt rename {diktat-rules => diktat-runner/diktat-runner-ktlint-engine}/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt (90%) 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 226ed29f65..0ee4fe2de3 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,6 +1,5 @@ package org.cqfn.diktat.common.config.reader -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import mu.KotlinLogging import java.io.IOException @@ -36,7 +35,7 @@ open class ApplicationProperties(propertiesFileName: String) { } companion object { - private val log = KotlinLogging.loggerWithKtlintConfig(ApplicationProperties::class) + private val log = KotlinLogging.logger {} private const val EXIT_STATUS_MISSING_PROPERTIES = 4 } } 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 4d0d918091..aea34ca0e3 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,6 +1,5 @@ package org.cqfn.diktat.common.config.reader -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import mu.KotlinLogging import java.io.BufferedReader @@ -57,6 +56,6 @@ abstract class JsonResourceConfigReader { protected abstract fun parseResource(fileStream: BufferedReader): T companion object { - private val log = KotlinLogging.loggerWithKtlintConfig(JsonResourceConfigReader::class) + private val log = KotlinLogging.logger { } } } 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 c063e840ce..a6f51f701c 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 @@ -6,7 +6,6 @@ package org.cqfn.diktat.common.config.rules import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader import org.cqfn.diktat.common.config.rules.RulesConfigReader.Companion.log -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration @@ -106,7 +105,7 @@ open class RulesConfigReader(override val classLoader: ClassLoader) : JsonResour } companion object { - internal val log: KLogger = KotlinLogging.loggerWithKtlintConfig(RulesConfigReader::class) + internal val log: KLogger = KotlinLogging.logger { } } } diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index 0d70ded181..f2a4cad856 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -13,13 +13,8 @@ plugins { dependencies { implementation(kotlin("gradle-plugin-api")) + implementation(projects.diktatRules) implementation(projects.diktatRunner.diktatRunnerKtlintEngine) - implementation(libs.ktlint.core) - implementation(libs.ktlint.reporter.plain) - implementation(libs.ktlint.reporter.sarif) - implementation(libs.ktlint.reporter.json) - implementation(libs.ktlint.reporter.html) - implementation(libs.ktlint.reporter.baseline) // merge sarif reports implementation(libs.sarif4k.jvm) implementation(libs.kotlinx.serialization.json) diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt index 15e25dafa5..dbf88c89d6 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/tasks/DiktatTaskBase.kt @@ -16,6 +16,7 @@ import org.cqfn.diktat.plugin.gradle.DiktatExtension import org.cqfn.diktat.plugin.gradle.DiktatJavaExecTaskBase import org.cqfn.diktat.plugin.gradle.getOutputFile import org.cqfn.diktat.plugin.gradle.getReporterType +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import generated.DIKTAT_VERSION import generated.KTLINT_VERSION @@ -75,8 +76,16 @@ abstract class DiktatTaskBase( * [DiktatProcessor] created from [extension] */ @get:Internal + internal val diktatRuleSet by lazy { + DiktatRuleSetFactoryImpl().create(extension.diktatConfigFile.toPath()) + } + + /** + * [DiktatProcessor] created from [diktatRuleSet] + */ + @get:Internal internal val diktatProcessor: DiktatProcessor by lazy { - DiktatProcessorFactoryImpl().create(extension.diktatConfigFile.toPath()) + DiktatProcessorFactoryImpl().invoke(diktatRuleSet) } /** diff --git a/diktat-maven-plugin/build.gradle.kts b/diktat-maven-plugin/build.gradle.kts index 1e0034d8ef..a372d40d13 100644 --- a/diktat-maven-plugin/build.gradle.kts +++ b/diktat-maven-plugin/build.gradle.kts @@ -17,12 +17,7 @@ dependencies { implementation(libs.kotlin.stdlib.jdk8) implementation(projects.diktatRules) implementation(projects.diktatRunner.diktatRunnerKtlintEngine) - implementation(libs.ktlint.core) - implementation(libs.ktlint.reporter.plain) - implementation(libs.ktlint.reporter.sarif) - implementation(libs.ktlint.reporter.json) - implementation(libs.ktlint.reporter.html) - implementation(libs.ktlint.reporter.baseline) + testImplementation(libs.junit.jupiter.api) testImplementation(libs.junit.vintage.engine) testImplementation(libs.junit.jupiter.extension.itf) 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 222083cd94..02d114cd6a 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 @@ -12,9 +12,11 @@ import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener import org.cqfn.diktat.api.DiktatProcessorListener.Companion.countErrorsAsProcessorListener import org.cqfn.diktat.api.DiktatReporterFactory +import org.cqfn.diktat.api.DiktatRuleSetFactory import org.cqfn.diktat.ktlint.DiktatBaselineFactoryImpl import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import org.apache.maven.execution.MavenSession import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugin.Mojo @@ -119,8 +121,11 @@ abstract class DiktatBaseMojo : AbstractMojo() { ) val sourceRootDir = mavenProject.basedir.parentFile.toPath() + val diktatRuleSet by lazy { + DiktatRuleSetFactoryImpl().create(configFile) + } val diktatProcessor by lazy { - DiktatProcessorFactoryImpl().create(configFile) + DiktatProcessorFactoryImpl().invoke(diktatRuleSet) } val baselineFactory: DiktatBaselineFactory by lazy { DiktatBaselineFactoryImpl() diff --git a/diktat-rules/build.gradle.kts b/diktat-rules/build.gradle.kts index cde215dbc4..e575c9074e 100644 --- a/diktat-rules/build.gradle.kts +++ b/diktat-rules/build.gradle.kts @@ -10,6 +10,8 @@ project.description = "The main diktat ruleset" dependencies { api(projects.diktatCommon) + api(projects.diktatRunner.diktatRunnerApi) + implementation(projects.diktatRunner.diktatRunnerKtlintEngine) testImplementation(projects.diktatTestFramework) api(libs.ktlint.core) implementation(libs.kotlin.stdlib.jdk8) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetProviderWrapper.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetProviderWrapper.kt deleted file mode 100644 index 75a42e1272..0000000000 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetProviderWrapper.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider -import com.pinterest.ktlint.core.RuleSet -import com.pinterest.ktlint.core.RuleSetProvider - -/** - * This is a wrapper around __KtLint__'s [RuleSetProvider]. - */ -class KtLintRuleSetProviderWrapper private constructor( - private val diktatRuleSetFactory: DiktatRuleSetProvider, -) : RuleSetProvider { - override fun get(): RuleSet = diktatRuleSetFactory().toKtLint() - - companion object { - /** - * @return __KtLint__'s [RuleSetProvider] created from [DiktatRuleSetProvider] - */ - fun DiktatRuleSetProvider.toKtLint(): RuleSetProvider = KtLintRuleSetProviderWrapper(this) - } -} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRule.kt index 2ef540c1a4..ac6b8f2bad 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRule.kt @@ -1,9 +1,9 @@ package org.cqfn.diktat.ruleset.rules +import org.cqfn.diktat.api.DiktatErrorEmitter import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.isRuleEnabled import org.cqfn.diktat.common.utils.loggerWithKtlintConfig -import org.cqfn.diktat.ruleset.constants.EmitType import org.cqfn.diktat.ruleset.utils.getFilePath import com.pinterest.ktlint.core.Rule @@ -11,6 +11,7 @@ import mu.KotlinLogging import org.jetbrains.kotlin.com.intellij.lang.ASTNode private typealias DiktatConfigRule = org.cqfn.diktat.common.config.rules.Rule +private typealias DiktatRuleApi = org.cqfn.diktat.api.DiktatRule /** * This is a wrapper around _KtLint_ `Rule`. @@ -21,10 +22,10 @@ private typealias DiktatConfigRule = org.cqfn.diktat.common.config.rules.Rule */ @Suppress("TooGenericExceptionCaught") abstract class DiktatRule( - val id: String, + override val id: String, val configRules: List, private val inspections: List, -) { +) : DiktatRuleApi { /** * Default value is false */ @@ -43,21 +44,21 @@ abstract class DiktatRule( * @see visit * @see logic */ - lateinit var emitWarn: EmitType + lateinit var emitWarn: DiktatErrorEmitter /** * @param node * @param autoCorrect - * @param emit + * @param emitter * @throws Error */ @Suppress("TooGenericExceptionThrown") - fun visit( + override fun invoke( node: ASTNode, autoCorrect: Boolean, - emit: EmitType + emitter: DiktatErrorEmitter ) { - emitWarn = emit + emitWarn = emitter isFixMode = autoCorrect if (areInspectionsDisabled()) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt new file mode 100644 index 0000000000..d4f04283ee --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt @@ -0,0 +1,13 @@ +package org.cqfn.diktat.ruleset.rules + +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.api.DiktatRuleSetFactory + +/** + * A default implementation of [DiktatRuleSetFactory] + */ +class DiktatRuleSetFactoryImpl : DiktatRuleSetFactory { + override fun invoke(): DiktatRuleSet = DiktatRuleSetProvider().invoke() + + override fun create(configFile: String): DiktatRuleSet = DiktatRuleSetProvider(configFile).invoke() +} 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 c241895a94..5b8fb75adb 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 @@ -4,6 +4,7 @@ package org.cqfn.diktat.ruleset.rules +import org.cqfn.diktat.api.DiktatRuleSet import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF import org.cqfn.diktat.common.config.rules.DIKTAT_COMMON import org.cqfn.diktat.common.config.rules.DIKTAT_CONF_PROPERTY diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KtlintUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KtlintUtils.kt deleted file mode 100644 index 6d773bd279..0000000000 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KtlintUtils.kt +++ /dev/null @@ -1,73 +0,0 @@ -@file:Suppress( - "HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE", - "Deprecation", -) - -package org.cqfn.diktat.ruleset.utils - -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.KtLint.ExperimentalParams -import com.pinterest.ktlint.core.LintError -import com.pinterest.ktlint.core.RuleSetProvider -import mu.KotlinLogging -import org.intellij.lang.annotations.Language - -@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") -private val log = KotlinLogging.loggerWithKtlintConfig {} - -@Suppress("TYPE_ALIAS") -val defaultCallback: (lintError: LintError, corrected: Boolean) -> Unit = { lintError, _ -> - log.warn("Received linting error: $lintError") -} - -typealias LintErrorCallback = (LintError, Boolean) -> Unit - -/** - * Enables ignoring autocorrected errors when in "fix" mode (i.e. when - * [KtLint.format] is invoked). - * - * Before version 0.47, _Ktlint_ only reported non-corrected errors in "fix" - * mode. - * Now, this has changed. - * - * @receiver the instance of _Ktlint_ parameters. - * @return the instance with the [callback][ExperimentalParams.cb] modified in - * such a way that it ignores corrected errors. - * @see KtLint.format - * @see ExperimentalParams.cb - * @since 1.2.4 - */ -fun ExperimentalParams.ignoreCorrectedErrors(): ExperimentalParams = - copy(cb = { error: LintError, corrected: Boolean -> - if (!corrected) { - cb(error, false) - } - }) - -/** - * @param ruleSetProviderRef - * @param text - * @param fileName - * @param cb callback to be called on unhandled [LintError]s - * @return formatted code - */ -@Suppress("LAMBDA_IS_NOT_LAST_PARAMETER") -fun format( - ruleSetProviderRef: () -> RuleSetProvider, - @Language("kotlin") text: String, - fileName: String, - cb: LintErrorCallback = defaultCallback -): String { - val ruleSets = listOf(ruleSetProviderRef().get()) - return KtLint.format( - ExperimentalParams( - text = text, - ruleSets = ruleSets, - fileName = fileName.removeSuffix("_copy"), - script = fileName.removeSuffix("_copy").endsWith("kts"), - cb = cb, - debug = true, - ).ignoreCorrectedErrors() - ) -} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/RulesConfigValidationTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/RulesConfigValidationTest.kt index deaf647025..decaa93a00 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/RulesConfigValidationTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/RulesConfigValidationTest.kt @@ -1,6 +1,5 @@ package org.cqfn.diktat.ruleset.smoke -import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider import org.cqfn.diktat.test.framework.util.deleteIfExistsSilently @@ -40,7 +39,7 @@ class RulesConfigValidationTest { """.trimMargin() ) val exception = assertThrows { - DiktatRuleSetProvider(file.absolutePath).toKtLint().get() + DiktatRuleSetProvider(file.absolutePath).invoke() } Assertions.assertEquals("Warning name in configuration file is invalid, did you mean ?", exception.message) } @@ -55,7 +54,7 @@ class RulesConfigValidationTest { """.trimMargin() ) assertThrows { - DiktatRuleSetProvider(file.absolutePath).toKtLint().get() + DiktatRuleSetProvider(file.absolutePath).invoke() } } @@ -71,6 +70,6 @@ class RulesConfigValidationTest { | isIncludeHeader: Fslse """.trimMargin() ) - DiktatRuleSetProvider(file.absolutePath).toKtLint().get() + DiktatRuleSetProvider(file.absolutePath).invoke() } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt index 3621ccbf8e..733293d0cf 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt @@ -8,21 +8,15 @@ package org.cqfn.diktat.util +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.api.DiktatRuleSetFactory import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.RulesConfigReader -import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.delegatee import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.rules.DiktatRuleSet import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider import org.cqfn.diktat.test.framework.util.filterContentMatches -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet -import com.pinterest.ktlint.core.RuleSetProvider import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.nio.file.Path @@ -35,12 +29,15 @@ import kotlin.io.path.walk /** * simple class for emulating RuleSetProvider to inject .yml rule configuration and mock this part of code */ -class DiktatRuleSetProvider4Test(private val ruleSupplier: (rulesConfigList: List) -> DiktatRule, - rulesConfigList: List?) : RuleSetProvider { +class DiktatRuleSetProvider4Test( + private val ruleSupplier: (rulesConfigList: List) -> DiktatRule, + rulesConfigList: List?, +) : DiktatRuleSetFactory { private val rulesConfigList: List? = rulesConfigList ?: RulesConfigReader(javaClass.classLoader).readResource("diktat-analysis.yml") - @Suppress("OVERRIDE_DEPRECATION") - override fun get(): RuleSet = DiktatRuleSet(listOf(ruleSupplier.invoke(rulesConfigList ?: emptyList()))).toKtLint() + override fun invoke(): DiktatRuleSet = DiktatRuleSet(listOf(ruleSupplier.invoke(rulesConfigList ?: emptyList()))) + + override fun create(configFile: String): DiktatRuleSet = throw NotImplementedError("Method is not supported for testing") } class DiktatRuleSetProviderTest { @@ -57,18 +54,9 @@ class DiktatRuleSetProviderTest { .filterNot { it in ignoredFileNames } .toList() val ruleNames = DiktatRuleSetProvider() - .toKtLint() - .get() + .invoke() + .rules .asSequence() - .onEachIndexed { index, rule -> - if (index != 0) { - Assertions.assertTrue( - rule.visitorModifiers.any { it is Rule.VisitorModifier.RunAfterRule }, - "Rule ${rule.id} doesn't contain Rule.VisitorModifier.RunAfterRule" - ) - } - } - .map { it.delegatee() } .map { it::class.simpleName } .filterNotNull() .filterNot { it in ignoredRuleNames } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt index 060af0de34..1432a66878 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt @@ -4,14 +4,13 @@ package org.cqfn.diktat.util +import org.cqfn.diktat.api.DiktatCallback import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ktlint.defaultCallback +import org.cqfn.diktat.ktlint.format import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.LintErrorCallback -import org.cqfn.diktat.ruleset.utils.defaultCallback -import org.cqfn.diktat.ruleset.utils.format import org.cqfn.diktat.test.framework.processing.FileComparisonResult import org.cqfn.diktat.test.framework.processing.TestComparatorUnit -import com.pinterest.ktlint.core.Rule import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions import java.nio.file.Path @@ -25,7 +24,7 @@ open class FixTestBase( resourceFilePath: String, ruleSupplier: (rulesConfigList: List) -> DiktatRule, defaultRulesConfigList: List? = null, - cb: LintErrorCallback = defaultCallback, + cb: DiktatCallback = defaultCallback, ) { /** * testComparatorUnit @@ -35,7 +34,7 @@ open class FixTestBase( resourceFilePath = resourceFilePath, function = { expectedText, testFilePath -> format( - ruleSetProviderRef = { DiktatRuleSetProvider4Test(ruleSupplier, overrideRulesConfigList ?: defaultRulesConfigList) }, + ruleSetSupplier = { DiktatRuleSetProvider4Test(ruleSupplier, overrideRulesConfigList ?: defaultRulesConfigList).invoke() }, text = expectedText, fileName = testFilePath, cb = cb, diff --git a/diktat-ruleset/build.gradle.kts b/diktat-ruleset/build.gradle.kts index 43fb22d3aa..03a00cc78f 100644 --- a/diktat-ruleset/build.gradle.kts +++ b/diktat-ruleset/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-compiler-embeddable") } + implementation(projects.diktatRunner.diktatRunnerKtlintEngine) testImplementation(projects.diktatTestFramework) testImplementation(libs.kotlin.stdlib.common) testImplementation(libs.kotlin.stdlib.jdk7) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt similarity index 78% rename from diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt rename to diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt index 1bfcab8f82..1f6de96a76 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt +++ b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt @@ -1,8 +1,8 @@ package org.cqfn.diktat.ruleset.rules -import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider +import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint /** * [RuleSetProvider] that provides diKTat ruleset. @@ -13,5 +13,5 @@ import com.pinterest.ktlint.core.RuleSetProvider * This class is registered in [resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider] */ class DiktatRuleSetProviderSpi : RuleSetProvider { - override fun get(): RuleSet = DiktatRuleSetProvider().toKtLint().get() + override fun get(): RuleSet = DiktatRuleSetProvider().invoke().toKtLint() } diff --git a/diktat-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider b/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider similarity index 100% rename from diktat-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider rename to diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider diff --git a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt index b189a4a257..4ce2541e83 100644 --- a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -1,8 +1,9 @@ package org.cqfn.diktat.ruleset.smoke -import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint +import org.cqfn.diktat.api.DiktatError +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap +import org.cqfn.diktat.ktlint.format import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider -import org.cqfn.diktat.ruleset.utils.format import org.cqfn.diktat.test.framework.processing.TestComparatorUnit import com.pinterest.ktlint.core.LintError import org.junit.jupiter.api.Assertions @@ -16,7 +17,7 @@ import kotlin.io.path.absolutePathString * may change after some changes to text or other rules. */ class DiktatSmokeTest : DiktatSmokeTestBase() { - private val unfixedLintErrors: MutableList = mutableListOf() + private val unfixedLintErrors: MutableList = mutableListOf() override fun fixAndCompare( config: Path, @@ -37,14 +38,14 @@ class DiktatSmokeTest : DiktatSmokeTestBase() { } override fun assertUnfixedLintErrors(lintErrorsConsumer: (List) -> Unit) { - lintErrorsConsumer(unfixedLintErrors) + lintErrorsConsumer(unfixedLintErrors.map { it.unwrap() }) } private fun getTestComparatorUnit(config: Path) = TestComparatorUnit( resourceFilePath = RESOURCE_FILE_PATH, function = { expectedText, testFilePath -> format( - ruleSetProviderRef = { DiktatRuleSetProvider(config.absolutePathString()).toKtLint() }, + ruleSetSupplier = { DiktatRuleSetProvider(config.absolutePathString()).invoke() }, text = expectedText, fileName = testFilePath, cb = { lintError, _ -> unfixedLintErrors.add(lintError) }, diff --git a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt index 785b50ccff..acc1043e30 100644 --- a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt +++ b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt @@ -6,6 +6,7 @@ package org.cqfn.diktat.ruleset.smoke +import org.cqfn.diktat.api.DiktatError import org.cqfn.diktat.common.config.rules.DIKTAT_COMMON import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.common.config.rules.RulesConfig diff --git a/diktat-runner/diktat-runner-api/build.gradle.kts b/diktat-runner/diktat-runner-api/build.gradle.kts index 549187cefe..6877fdf899 100644 --- a/diktat-runner/diktat-runner-api/build.gradle.kts +++ b/diktat-runner/diktat-runner-api/build.gradle.kts @@ -1,13 +1,11 @@ -@Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") - alias(libs.plugins.kotlin.plugin.serialization) } project.description = "This module builds diktat-runner-api" dependencies { - implementation(projects.diktatRules) + implementation(libs.kotlin.compiler.embeddable) } diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt index 42001b0046..8363826ed4 100644 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt @@ -1,29 +1,15 @@ package org.cqfn.diktat -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider -import java.nio.file.Path -import kotlin.io.path.absolutePathString +import org.cqfn.diktat.api.DiktatRuleSet /** - * A factory to create [DiktatProcessor] using [DiktatRuleSetProvider] + * A factory to create [DiktatProcessor] using [DiktatRuleSet] */ @FunctionalInterface -fun interface DiktatProcessorFactory : Function1 { +interface DiktatProcessorFactory : Function1 { /** - * @param diktatRuleSetProvider - * @return created [DiktatProcessor] using [diktatRuleSetProvider] + * @param diktatRuleSet + * @return created [DiktatProcessor] using [DiktatRuleSet] */ - override fun invoke(diktatRuleSetProvider: DiktatRuleSetProvider): DiktatProcessor - - /** - * @param configFile a path to file with configuration for diktat (`diktat-analysis.yml`) - * @return created [DiktatProcessor] using [configFile] - */ - fun create(configFile: String): DiktatProcessor = invoke(DiktatRuleSetProvider(configFile)) - - /** - * @param configFile a file with configuration for diktat (`diktat-analysis.yml`) - * @return created [DiktatProcessor] using [configFile] - */ - fun create(configFile: Path): DiktatProcessor = create(configFile.absolutePathString()) + override operator fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor } diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt new file mode 100644 index 0000000000..4ddd077357 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt @@ -0,0 +1,20 @@ +package org.cqfn.diktat.api + + +/** + * The **file-specific** error emitter, initialized and used in [DiktatRule] implementations. + * + * Since the file is indirectly a part of the state of a `DiktatRule`, the same + * `DiktatRule` instance should **never be re-used** to check more than a single + * file, or confusing effects (incl. race conditions) will occur. + * + * @see DiktatRule + */ +fun interface DiktatErrorEmitter : Function3 { + /** + * @param offset + * @param errorMessage + * @param canBeAutoCorrected + */ + override fun invoke(offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) +} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatLogLevel.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatLogLevel.kt deleted file mode 100644 index 6e6e18641d..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatLogLevel.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.cqfn.diktat.api - -/** - * Log level of `diktat processing` - */ -enum class DiktatLogLevel { - DEBUG, - INFO, - ; -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt new file mode 100644 index 0000000000..c63e0dfba7 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt @@ -0,0 +1,26 @@ +package org.cqfn.diktat.api + +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** +* This is a base interface for diktat's rules +*/ +interface DiktatRule : Function3 { + /** + * A unique ID of this rule. + */ + val id: String + + /** + * This method is going to be executed for each node in AST (in DFS fashion). + * + * @param node AST node + * @param autoCorrect indicates whether rule should attempt autocorrection + * @param emitter a way for rule to notify about a violation (lint error) + */ + override fun invoke( + node: ASTNode, + autoCorrect: Boolean, + emitter: DiktatErrorEmitter, + ) +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSet.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt similarity index 79% rename from diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSet.kt rename to diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt index 553dd65f2e..18700f5a24 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSet.kt +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt @@ -1,4 +1,4 @@ -package org.cqfn.diktat.ruleset.rules +package org.cqfn.diktat.api /** * A group of [DiktatRule]'s as a single set. diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt new file mode 100644 index 0000000000..5f66e87e64 --- /dev/null +++ b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt @@ -0,0 +1,26 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path +import kotlin.io.path.absolutePathString + +/** + * A factory which creates a [DiktatRuleSet]. + */ +interface DiktatRuleSetFactory : Function0 { + /** + * @return the default instance of [DiktatRuleSet] + */ + override operator fun invoke(): DiktatRuleSet + + /** + * @param configFile a path to file with configuration for diktat (`diktat-analysis.yml`) + * @return created [DiktatRuleSet] using [configFile] + */ + fun create(configFile: String): DiktatRuleSet + + /** + * @param configFile a file with configuration for diktat (`diktat-analysis.yml`) + * @return created [DiktatRuleSet] using [configFile] + */ + fun create(configFile: Path): DiktatRuleSet = create(configFile.absolutePathString()) +} diff --git a/diktat-runner/diktat-runner-cli/build.gradle.kts b/diktat-runner/diktat-runner-cli/build.gradle.kts index 12d376f8dc..a85a2973bc 100644 --- a/diktat-runner/diktat-runner-cli/build.gradle.kts +++ b/diktat-runner/diktat-runner-cli/build.gradle.kts @@ -1,7 +1,9 @@ +@Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") + alias(libs.plugins.kotlin.plugin.serialization) } project.description = "This module builds diktat-runner implementation using ktlint as CLI" diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt index d4821c5b30..667514cdb2 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -4,7 +4,7 @@ package org.cqfn.diktat -import org.cqfn.diktat.api.DiktatMode +import org.cqfn.diktat.cli.DiktatMode import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.cli.DiktatProperties import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt similarity index 88% rename from diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt rename to diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt index aabe4ea9c5..1618e008f4 100644 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatMode.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt @@ -1,4 +1,4 @@ -package org.cqfn.diktat.api +package org.cqfn.diktat.cli import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 29753bfe02..593880e368 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -1,6 +1,5 @@ package org.cqfn.diktat.cli -import org.cqfn.diktat.api.DiktatMode import org.cqfn.diktat.api.DiktatReporter import org.cqfn.diktat.api.DiktatReporterFactory import org.cqfn.diktat.common.config.rules.DIKTAT @@ -46,12 +45,25 @@ data class DiktatProperties( fun reporter( diktatReporterFactory: DiktatReporterFactory, sourceRootDir: Path, - ): DiktatReporter = buildReporter( - diktatReporterFactory, reporterProviderId, - output, - colorNameInPlain, groupByFileInPlain, - sourceRootDir, - ) + ): DiktatReporter { + val outputStream = output + ?.let { Paths.get(it) } + ?.also { it.parent.createDirectories() } + ?.outputStream() + ?.let { PrintStream(it) } + ?: System.out + return if (reporterProviderId == diktatReporterFactory.plainId) { + diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) + } else { + require(colorNameInPlain == null) { + "colorization is applicable only for plain reporter" + } + require(!groupByFileInPlain) { + "groupByFile is applicable only for plain reporter" + } + diktatReporterFactory.invoke(reporterProviderId, outputStream, sourceRootDir) + } + } /** * Configure logger level using [logLevel] @@ -209,32 +221,5 @@ data class DiktatProperties( .getResource(resourceName) ?.readText() ?: error("Resource $resourceName not found") - - private fun buildReporter( - diktatReporterFactory: DiktatReporterFactory, - reporterProviderId: String, - output: String?, - colorNameInPlain: String?, - groupByFileInPlain: Boolean, - sourceRootDir: Path, - ): DiktatReporter { - val outputStream = output - ?.let { Paths.get(it) } - ?.also { it.parent.createDirectories() } - ?.outputStream() - ?.let { PrintStream(it) } - ?: System.out - return if (reporterProviderId == diktatReporterFactory.plainId) { - diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) - } else { - require(colorNameInPlain == null) { - "colorization is applicable only for plain reporter" - } - require(!groupByFileInPlain) { - "groupByFile is applicable only for plain reporter" - } - diktatReporterFactory.invoke(reporterProviderId, outputStream, sourceRootDir) - } - } } } diff --git a/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts b/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts index a32060b715..a12bdc533d 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts +++ b/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts @@ -8,7 +8,7 @@ project.description = "This module builds diktat-runner implementation using ktl dependencies { api(projects.diktatRunner.diktatRunnerApi) - implementation(projects.diktatRules) + implementation(projects.diktatCommon) implementation(libs.ktlint.core) implementation(libs.ktlint.reporter.baseline) implementation(libs.ktlint.reporter.checkstyle) @@ -16,4 +16,9 @@ dependencies { implementation(libs.ktlint.reporter.json) implementation(libs.ktlint.reporter.plain) implementation(libs.ktlint.reporter.sarif) + + testImplementation(libs.junit.jupiter) + testImplementation(libs.junit.platform.suite) + testImplementation(libs.assertj.core) + testImplementation(libs.mockito) } diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt similarity index 100% rename from diktat-common/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt rename to diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt index 52348ed0ca..4b309ad50f 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt @@ -9,7 +9,7 @@ import com.pinterest.ktlint.core.LintError * @property lintError */ data class DiktatErrorImpl( - val lintError: LintError + private val lintError: LintError ) : DiktatError { override fun getLine(): Int = lintError.line diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt index f52c8ba301..fca4f39938 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt @@ -3,9 +3,8 @@ package org.cqfn.diktat.ktlint import org.cqfn.diktat.DiktatProcessor import org.cqfn.diktat.DiktatProcessorFactory import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.api.DiktatRuleSet import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider -import org.cqfn.diktat.ruleset.utils.LintErrorCallback import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.api.EditorConfigOverride import java.nio.charset.StandardCharsets @@ -14,23 +13,23 @@ import kotlin.io.path.absolutePathString import kotlin.io.path.readText /** - * A factory to create [DiktatProcessor] using [DiktatRuleSetProvider] using `KtLint` + * A factory to create [DiktatProcessor] using [DiktatProcessorFactory] and `KtLint` as engine */ class DiktatProcessorFactoryImpl : DiktatProcessorFactory { - override fun invoke(diktatRuleSetProvider: DiktatRuleSetProvider): DiktatProcessor = object : DiktatProcessor() { - override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(ktLintParams(diktatRuleSetProvider, file, callback.unwrap())) - override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(ktLintParams(diktatRuleSetProvider, file, callback.unwrap())) + override fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor = object : DiktatProcessor() { + override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(ktLintParams(diktatRuleSet, file, callback.unwrap())) + override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(ktLintParams(diktatRuleSet, file, callback.unwrap())) } @Suppress("DEPRECATION") private fun ktLintParams( - diktatRuleSetProvider: DiktatRuleSetProvider, + diktatRuleSet: DiktatRuleSet, file: Path, callback: LintErrorCallback, ): KtLint.ExperimentalParams = KtLint.ExperimentalParams( fileName = file.absolutePathString(), text = file.readText(StandardCharsets.UTF_8), - ruleSets = setOf(diktatRuleSetProvider().toKtLint()), + ruleSets = setOf(diktatRuleSet.toKtLint()), userData = emptyMap(), cb = callback, script = false, // internal API of KtLint diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt similarity index 92% rename from diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt rename to diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt index e27fdaf978..46bf92c8f9 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt @@ -1,8 +1,8 @@ package org.cqfn.diktat.ktlint +import org.cqfn.diktat.api.DiktatRule +import org.cqfn.diktat.api.DiktatRuleSet import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID -import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.rules.DiktatRuleSet import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.RuleSet diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt similarity index 83% rename from diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt rename to diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt index 97b905c749..19d3e7716a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt @@ -1,12 +1,13 @@ package org.cqfn.diktat.ktlint +import org.cqfn.diktat.api.DiktatRule import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId -import org.cqfn.diktat.ruleset.constants.EmitType -import org.cqfn.diktat.ruleset.rules.DiktatRule import com.pinterest.ktlint.core.Rule import org.jetbrains.kotlin.com.intellij.lang.ASTNode +private typealias EmitType = (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + /** * This is a wrapper around __KtLint__'s [Rule] which adjusts visitorModifiers to keep order with prevRule. * @property rule @@ -26,7 +27,7 @@ class KtLintRuleWrapper( node: ASTNode, autoCorrect: Boolean, emit: EmitType, - ) = rule.visit(node, autoCorrect, emit) + ) = rule.invoke(node, autoCorrect, emit) companion object { private fun createVisitorModifiers( @@ -50,6 +51,6 @@ class KtLintRuleWrapper( /** * @return a rule to which a logic is delegated */ - internal fun Rule.delegatee(): DiktatRule = (this as? KtLintRuleWrapper)?.rule ?: error("Provided rule ${javaClass.simpleName} is not wrapped by diktat") + internal fun Rule.unwrap(): DiktatRule = (this as? KtLintRuleWrapper)?.rule ?: error("Provided rule ${javaClass.simpleName} is not wrapped by diktat") } } diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt index f655d6549d..cff1ce20b3 100644 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt @@ -4,12 +4,93 @@ package org.cqfn.diktat.ktlint +import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap +import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.KtLint.ExperimentalParams +import com.pinterest.ktlint.core.LintError +import mu.KotlinLogging +import org.intellij.lang.annotations.Language import java.nio.file.Path import kotlin.io.path.invariantSeparatorsPathString import kotlin.io.path.relativeTo +typealias LintErrorCallback = (LintError, Boolean) -> Unit + +private val log = KotlinLogging.logger { } + +val defaultCallback = DiktatCallback { error, _ -> + log.warn { "Received linting error: $error" } +} + /** * @param sourceRootDir * @return relative path to [sourceRootDir] as [String] */ fun Path.relativePathStringTo(sourceRootDir: Path): String = relativeTo(sourceRootDir).invariantSeparatorsPathString + +/** + * @return [DiktatCallback] from KtLint [LintErrorCallback] + */ +fun LintErrorCallback.wrap(): DiktatCallback = DiktatCallback { error, isCorrected -> + this(error.unwrap(), isCorrected) +} + +/** + * @return KtLint [LintErrorCallback] from [DiktatCallback] or exception + */ +fun DiktatCallback.unwrap(): LintErrorCallback = { error, isCorrected -> + this(error.wrap(), isCorrected) +} + +/** + * Enables ignoring autocorrected errors when in "fix" mode (i.e. when + * [KtLint.format] is invoked). + * + * Before version 0.47, _Ktlint_ only reported non-corrected errors in "fix" + * mode. + * Now, this has changed. + * + * @receiver the instance of _Ktlint_ parameters. + * @return the instance with the [callback][ExperimentalParams.cb] modified in + * such a way that it ignores corrected errors. + * @see KtLint.format + * @see ExperimentalParams.cb + * @since 1.2.4 + */ +private fun ExperimentalParams.ignoreCorrectedErrors(): ExperimentalParams = + copy(cb = { error: LintError, corrected: Boolean -> + if (!corrected) { + cb(error, false) + } + }) + +/** + * @param ruleSetSupplier + * @param text + * @param fileName + * @param cb callback to be called on unhandled [LintError]s + * @return formatted code + */ +@Suppress("LAMBDA_IS_NOT_LAST_PARAMETER") +fun format( + ruleSetSupplier: () -> DiktatRuleSet, + @Language("kotlin") text: String, + fileName: String, + cb: DiktatCallback = defaultCallback +): String { + val ruleSets = listOf(ruleSetSupplier().toKtLint()) + return KtLint.format( + ExperimentalParams( + text = text, + ruleSets = ruleSets, + fileName = fileName.removeSuffix("_copy"), + script = fileName.removeSuffix("_copy").endsWith("kts"), + cb = cb.unwrap(), + debug = true, + ).ignoreCorrectedErrors() + ) +} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt deleted file mode 100644 index 300c3995dd..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/LintErrorCallbackWrapper.kt +++ /dev/null @@ -1,24 +0,0 @@ -/** - * This file contains utility methods for LintErrorCallback - */ - -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap -import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap -import org.cqfn.diktat.ruleset.utils.LintErrorCallback - -/** - * @return [DiktatCallback] from KtLint [LintErrorCallback] - */ -fun LintErrorCallback.wrap(): DiktatCallback = DiktatCallback { error, isCorrected -> - this(error.unwrap(), isCorrected) -} - -/** - * @return KtLint [LintErrorCallback] from [DiktatCallback] or exception - */ -fun DiktatCallback.unwrap(): LintErrorCallback = { error, isCorrected -> - this(error.wrap(), isCorrected) -} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt b/diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt similarity index 90% rename from diktat-rules/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt rename to diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt index cc0b9525e1..3d5014391e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt +++ b/diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt @@ -1,11 +1,11 @@ package org.cqfn.diktat.ktlint +import org.cqfn.diktat.api.DiktatErrorEmitter +import org.cqfn.diktat.api.DiktatRule +import org.cqfn.diktat.api.DiktatRuleSet import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.delegatee -import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.rules.DiktatRuleSet -import org.cqfn.diktat.util.TEST_FILE_NAME +import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.unwrap import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.Rule import org.assertj.core.api.Assertions.assertThat @@ -37,7 +37,7 @@ class KtLintRuleSetWrapperTest { val orderedRule2 = orderedRuleSetIterator.next() Assertions.assertFalse(orderedRuleSetIterator.hasNext(), "Extra elements after ordering") - Assertions.assertEquals(rule1, orderedRule1.delegatee(), "First rule is modified") + Assertions.assertEquals(rule1, orderedRule1.unwrap(), "First rule is modified") orderedRule2.visitorModifiers .filterIsInstance() @@ -86,7 +86,7 @@ class KtLintRuleSetWrapperTest { KtLint.lint( KtLint.ExperimentalParams( - fileName = TEST_FILE_NAME, + fileName = "TestFileName.kt", text = code, ruleSets = listOf(ruleSet), cb = { _, _ -> }, @@ -150,8 +150,10 @@ class KtLintRuleSetWrapperTest { private fun mockRule( id: String, onVisit: (DiktatRule) -> Unit = { } - ): DiktatRule = object : DiktatRule(id.qualifiedWithRuleSetId(), emptyList(), emptyList()) { - override fun logic(node: ASTNode) { + ): DiktatRule = object : DiktatRule { + override val id: String + get() = id + override fun invoke(node: ASTNode, autoCorrect: Boolean, emitter: DiktatErrorEmitter) { onVisit(this) } } 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 72e8a420c5..84bcc48509 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 @@ -2,7 +2,6 @@ package org.cqfn.diktat.test.framework.config import org.cqfn.diktat.common.cli.CliArgument import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import mu.KotlinLogging import org.apache.commons.cli.CommandLine @@ -17,7 +16,6 @@ import java.io.IOException import java.util.stream.Collectors import kotlin.system.exitProcess -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @@ -87,7 +85,6 @@ class TestArgumentsReader( * @param fileStream a [BufferedReader] representing input JSON * @return list of [CliArgument]s */ - @OptIn(ExperimentalSerializationApi::class) @Throws(IOException::class) override fun parseResource(fileStream: BufferedReader): List { val jsonValue = fileStream.lines().collect(Collectors.joining()) @@ -96,6 +93,6 @@ class TestArgumentsReader( companion object { @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") - private val log = KotlinLogging.loggerWithKtlintConfig {} + private val log = KotlinLogging.logger {} } } From 019395bcf50ae8c68658c1b9c8273ea75e9092b7 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 3 Apr 2023 16:15:34 +0300 Subject: [PATCH 08/34] fixed DiktatMain --- .../src/main/kotlin/org/cqfn/diktat/DiktatMain.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt index 667514cdb2..780c3ff04f 100644 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -9,6 +9,7 @@ import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.cli.DiktatProperties import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript import org.cqfn.diktat.util.tryToPathIfExists import org.cqfn.diktat.util.walkByGlob @@ -19,7 +20,6 @@ import java.nio.file.Paths import kotlin.io.path.readText import kotlin.io.path.writeText -@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") private val log = KotlinLogging.logger { } @Suppress( @@ -34,8 +34,8 @@ fun main(args: Array) { log.debug { "Loading diktatRuleSet using config ${properties.config}" } - val diktatProcessor = DiktatProcessorFactoryImpl() - .create(properties.config) + val diktatRuleSet = DiktatRuleSetFactoryImpl().create(properties.config) + val diktatProcessor = DiktatProcessorFactoryImpl().invoke(diktatRuleSet) val currentFolder = Paths.get(".") val reporter = properties.reporter(diktatReporterFactory, currentFolder) From 2a697d1f5120f90df983261f75944dbb8ed472b5 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 3 Apr 2023 16:37:08 +0300 Subject: [PATCH 09/34] fixed `loggerWithKtlintConfig` --- diktat-common/build.gradle.kts | 2 -- .../cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt | 8 ++++++++ .../diktat/test/framework/common/LocalCommandExecutor.kt | 3 +-- .../diktat/test/framework/processing/FileComparator.kt | 3 +-- .../diktat/test/framework/processing/TestCheckWarn.kt | 3 +-- .../test/framework/processing/TestComparatorUnit.kt | 4 +--- .../cqfn/diktat/test/framework/processing/TestCompare.kt | 3 +-- .../test/framework/processing/TestProcessingFactory.kt | 4 +--- .../org/cqfn/diktat/test/framework/util/TestUtils.kt | 4 +--- 9 files changed, 15 insertions(+), 19 deletions(-) diff --git a/diktat-common/build.gradle.kts b/diktat-common/build.gradle.kts index 3dd6061bcc..a4215a0a42 100644 --- a/diktat-common/build.gradle.kts +++ b/diktat-common/build.gradle.kts @@ -12,8 +12,6 @@ dependencies { api(libs.kaml) implementation(libs.apache.commons.cli) implementation(libs.kotlin.logging) - // ktlint-core is needed only for `initKtLintKLogger` method - implementation(libs.ktlint.core) testImplementation(libs.junit.jupiter) testImplementation(libs.assertj.core) } diff --git a/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt index 1f6de96a76..232b2b2adb 100644 --- a/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt +++ b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt @@ -3,6 +3,9 @@ package org.cqfn.diktat.ruleset.rules import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import com.pinterest.ktlint.core.initKtLintKLogger +import mu.KotlinLogging +import org.slf4j.Logger /** * [RuleSetProvider] that provides diKTat ruleset. @@ -13,5 +16,10 @@ import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint * This class is registered in [resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider] */ class DiktatRuleSetProviderSpi : RuleSetProvider { + init { + // need to init KtLintKLogger for ROOT logger + KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).initKtLintKLogger() + } + override fun get(): RuleSet = DiktatRuleSetProvider().invoke().toKtLint() } 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 4a6ba5a6c9..1b58e796e4 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,6 +1,5 @@ package org.cqfn.diktat.test.framework.common -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import mu.KotlinLogging import java.io.IOException @@ -38,6 +37,6 @@ class LocalCommandExecutor internal constructor(private val command: String) { companion object { @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") - private val log = KotlinLogging.loggerWithKtlintConfig {} + private val log = KotlinLogging.logger { } } } 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 ebbeda730a..52c5daf36c 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,5 @@ package org.cqfn.diktat.test.framework.processing -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import io.github.petertrr.diffutils.diff import io.github.petertrr.diffutils.patch.ChangeDelta import io.github.petertrr.diffutils.text.DiffRowGenerator @@ -111,7 +110,7 @@ class FileComparator( companion object { @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") - private val log = KotlinLogging.loggerWithKtlintConfig {} + private val log = KotlinLogging.logger {} /** * @param file file where to write these list to, separated with newlines. diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt index 8f26aa3fc0..b5429ba8b3 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt @@ -1,6 +1,5 @@ package org.cqfn.diktat.test.framework.processing -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import org.cqfn.diktat.test.framework.config.TestConfig import mu.KLogger import mu.KotlinLogging @@ -10,7 +9,7 @@ import mu.KotlinLogging */ class TestCheckWarn : TestCompare() { @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR", "MISSING_KDOC_CLASS_ELEMENTS") - override val log: KLogger = KotlinLogging.loggerWithKtlintConfig {} + override val log: KLogger = KotlinLogging.logger {} @Suppress( "UnusedPrivateMember", 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 f9d8900487..49fb848025 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,6 +1,5 @@ package org.cqfn.diktat.test.framework.processing -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import mu.KotlinLogging import java.io.IOException import java.nio.file.Path @@ -125,8 +124,7 @@ class TestComparatorUnit( } private companion object { - @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") - private val log = KotlinLogging.loggerWithKtlintConfig {} + private val log = KotlinLogging.logger {} /** * @param file the file whose content is to be read. 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 30ae35cf16..a616543cd3 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,6 +1,5 @@ package org.cqfn.diktat.test.framework.processing -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import org.cqfn.diktat.test.framework.common.ExecutionResult import org.cqfn.diktat.test.framework.common.TestBase import org.cqfn.diktat.test.framework.config.TestConfig @@ -18,7 +17,7 @@ import java.io.File @Suppress("ForbiddenComment") open class TestCompare : TestBase { @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR", "MISSING_KDOC_CLASS_ELEMENTS") - protected open val log: KLogger = KotlinLogging.loggerWithKtlintConfig {} + protected open val log: KLogger = KotlinLogging.logger {} private lateinit var expectedResult: File // testResultFile will be used if and only if --in-place option will be used 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 76b23cffd6..b3279467ae 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,6 +1,5 @@ package org.cqfn.diktat.test.framework.processing -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig import org.cqfn.diktat.test.framework.common.TestBase import org.cqfn.diktat.test.framework.config.TestArgumentsReader import org.cqfn.diktat.test.framework.config.TestConfig @@ -90,8 +89,7 @@ class TestProcessingFactory(private val argReader: TestArgumentsReader) { } companion object { - @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") - private val log = KotlinLogging.loggerWithKtlintConfig {} + private val log = KotlinLogging.logger {} private const val STATUS_FIVE = 5 private const val STATUS_THREE = 3 } diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt index bd0f40ad2e..e1ed526653 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt @@ -4,8 +4,6 @@ package org.cqfn.diktat.test.framework.util -import org.cqfn.diktat.common.utils.loggerWithKtlintConfig - import mu.KotlinLogging import java.io.File @@ -33,7 +31,7 @@ import kotlin.io.path.isDirectory import kotlin.io.path.isSameFileAs @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") -private val logger = KotlinLogging.loggerWithKtlintConfig {} +private val logger = KotlinLogging.logger {} /** * Deletes the file if it exists, retrying as necessary if the file is From 61f11037bf71960d1f78602a16604452f1c191a7 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 3 Apr 2023 18:53:11 +0300 Subject: [PATCH 10/34] renaming --- build.gradle.kts | 4 +- diktat-gradle-plugin/build.gradle.kts | 2 +- diktat-maven-plugin/build.gradle.kts | 2 +- diktat-rules/build.gradle.kts | 4 +- diktat-ruleset/build.gradle.kts | 2 +- .../diktat-runner-api/build.gradle.kts | 11 - .../kotlin/org/cqfn/diktat/DiktatProcessor.kt | 72 ------ .../org/cqfn/diktat/DiktatProcessorFactory.kt | 15 -- .../org/cqfn/diktat/api/DiktatBaseline.kt | 34 --- .../cqfn/diktat/api/DiktatBaselineFactory.kt | 28 --- .../org/cqfn/diktat/api/DiktatCallback.kt | 15 -- .../kotlin/org/cqfn/diktat/api/DiktatError.kt | 31 --- .../org/cqfn/diktat/api/DiktatErrorEmitter.kt | 20 -- .../diktat/api/DiktatProcessorListener.kt | 86 ------- .../cqfn/diktat/api/DiktatReporterFactory.kt | 52 ---- .../kotlin/org/cqfn/diktat/api/DiktatRule.kt | 26 -- .../org/cqfn/diktat/api/DiktatRuleSet.kt | 10 - .../cqfn/diktat/api/DiktatRuleSetFactory.kt | 26 -- .../diktat-runner-cli/build.gradle.kts | 23 -- .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 76 ------ .../kotlin/org/cqfn/diktat/cli/DiktatMode.kt | 16 -- .../org/cqfn/diktat/cli/DiktatProperties.kt | 225 ------------------ .../kotlin/org/cqfn/diktat/util/FileUtils.kt | 36 --- .../build.gradle.kts | 24 -- .../cqfn/diktat/common/utils/LoggingUtils.kt | 27 --- .../ktlint/DiktatBaselineFactoryImpl.kt | 42 ---- .../org/cqfn/diktat/ktlint/DiktatErrorImpl.kt | 37 --- .../ktlint/DiktatProcessorFactoryImpl.kt | 41 ---- .../ktlint/DiktatReporterFactoryImpl.kt | 81 ------- .../cqfn/diktat/ktlint/DiktatReporterImpl.kt | 35 --- .../diktat/ktlint/KtLintRuleSetWrapper.kt | 35 --- .../cqfn/diktat/ktlint/KtLintRuleWrapper.kt | 56 ----- .../org/cqfn/diktat/ktlint/KtLintUtils.kt | 96 -------- .../diktat/ktlint/KtLintRuleSetWrapperTest.kt | 161 ------------- settings.gradle.kts | 6 +- 35 files changed, 10 insertions(+), 1447 deletions(-) delete mode 100644 diktat-runner/diktat-runner-api/build.gradle.kts delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt delete mode 100644 diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt delete mode 100644 diktat-runner/diktat-runner-cli/build.gradle.kts delete mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt delete mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt delete mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt delete mode 100644 diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt delete mode 100644 diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 106d4180a5..dabf1b2fd9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,10 +32,10 @@ tasks.create("generateLibsForDiktatSnapshot") { val dir = rootProject.buildDir.resolve("diktat-snapshot") val dependencies = setOf( + rootProject.project(":diktat-api"), + rootProject.project(":diktat-ktlint-engine"), rootProject.project(":diktat-common"), rootProject.project(":diktat-rules"), - rootProject.project(":diktat-runner:diktat-runner-api"), - rootProject.project(":diktat-runner:diktat-runner-ktlint-engine"), rootProject.project(":diktat-gradle-plugin"), ) mustRunAfter(dependencies.map { "${it.path}:publishToMavenLocal" }) diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index f2a4cad856..407ac97b24 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -14,7 +14,7 @@ plugins { dependencies { implementation(kotlin("gradle-plugin-api")) implementation(projects.diktatRules) - implementation(projects.diktatRunner.diktatRunnerKtlintEngine) + implementation(projects.diktatKtlintEngine) // merge sarif reports implementation(libs.sarif4k.jvm) implementation(libs.kotlinx.serialization.json) diff --git a/diktat-maven-plugin/build.gradle.kts b/diktat-maven-plugin/build.gradle.kts index a372d40d13..d7c1d0dd16 100644 --- a/diktat-maven-plugin/build.gradle.kts +++ b/diktat-maven-plugin/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation(libs.kotlin.stdlib.jdk8) implementation(projects.diktatRules) - implementation(projects.diktatRunner.diktatRunnerKtlintEngine) + implementation(projects.diktatKtlintEngine) testImplementation(libs.junit.jupiter.api) testImplementation(libs.junit.vintage.engine) diff --git a/diktat-rules/build.gradle.kts b/diktat-rules/build.gradle.kts index e575c9074e..ec851fbe59 100644 --- a/diktat-rules/build.gradle.kts +++ b/diktat-rules/build.gradle.kts @@ -10,8 +10,8 @@ project.description = "The main diktat ruleset" dependencies { api(projects.diktatCommon) - api(projects.diktatRunner.diktatRunnerApi) - implementation(projects.diktatRunner.diktatRunnerKtlintEngine) + api(projects.diktatApi) + implementation(projects.diktatKtlintEngine) testImplementation(projects.diktatTestFramework) api(libs.ktlint.core) implementation(libs.kotlin.stdlib.jdk8) diff --git a/diktat-ruleset/build.gradle.kts b/diktat-ruleset/build.gradle.kts index 03a00cc78f..72b798d315 100644 --- a/diktat-ruleset/build.gradle.kts +++ b/diktat-ruleset/build.gradle.kts @@ -20,7 +20,7 @@ dependencies { exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-compiler-embeddable") } - implementation(projects.diktatRunner.diktatRunnerKtlintEngine) + implementation(projects.diktatKtlintEngine) testImplementation(projects.diktatTestFramework) testImplementation(libs.kotlin.stdlib.common) testImplementation(libs.kotlin.stdlib.jdk7) diff --git a/diktat-runner/diktat-runner-api/build.gradle.kts b/diktat-runner/diktat-runner-api/build.gradle.kts deleted file mode 100644 index 6877fdf899..0000000000 --- a/diktat-runner/diktat-runner-api/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") - id("org.cqfn.diktat.buildutils.code-quality-convention") - id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") -} - -project.description = "This module builds diktat-runner-api" - -dependencies { - implementation(libs.kotlin.compiler.embeddable) -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt deleted file mode 100644 index af942c488a..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.cqfn.diktat - -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.api.DiktatProcessorListener -import java.nio.file.Path - -/** - * Processor to run `diktat` - */ -abstract class DiktatProcessor { - /** - * Run `diktat fix` on provided [file] using [callback] for detected errors and returned formatted file content. - * - * @param file - * @param callback - * @return result of `diktat fix` - */ - abstract fun fix(file: Path, callback: DiktatCallback): String - - /** - * Run `diktat fix` for all [files] using [listener] during of processing and [formattedCodeHandler] to handle result of `diktat fix`. - * - * @param listener a listener which is called during processing. - * @param files - * @param formattedCodeHandler - */ - fun fixAll( - listener: DiktatProcessorListener = DiktatProcessorListener.empty, - files: Sequence, - formattedCodeHandler: (Path, String) -> Unit, - ) { - listener.beforeAll() - files.forEach { file -> - listener.before(file) - val formattedCode = fix(file) { error, isCorrected -> - listener.onError(file, error, isCorrected) - } - formattedCodeHandler(file, formattedCode) - listener.after(file) - } - listener.afterAll() - } - - /** - * Run `diktat check` on provided [file] using [callback] for detected errors. - * - * @param file - * @param callback - */ - abstract fun check(file: Path, callback: DiktatCallback) - - /** - * Run `diktat check` for all [files] using [listener] during of processing. - * - * @param listener a listener which is called during processing. - * @param files - */ - fun checkAll( - listener: DiktatProcessorListener = DiktatProcessorListener.empty, - files: Sequence, - ) { - listener.beforeAll() - files.forEach { file -> - listener.before(file) - check(file) { error, isCorrected -> - listener.onError(file, error, isCorrected) - } - listener.after(file) - } - listener.afterAll() - } -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt deleted file mode 100644 index 8363826ed4..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.cqfn.diktat - -import org.cqfn.diktat.api.DiktatRuleSet - -/** - * A factory to create [DiktatProcessor] using [DiktatRuleSet] - */ -@FunctionalInterface -interface DiktatProcessorFactory : Function1 { - /** - * @param diktatRuleSet - * @return created [DiktatProcessor] using [DiktatRuleSet] - */ - override operator fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt deleted file mode 100644 index 1f7fbb9e1e..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqfn.diktat.api - -import java.nio.file.Path - -/** - * A base interface for Baseline - */ -fun interface DiktatBaseline { - /** - * @param file - * @return a set of [DiktatError] found in baseline by [file] - */ - fun errorsByFile(file: Path): Set - - companion object { - /** - * Empty [DiktatBaseline] - */ - val empty: DiktatBaseline = DiktatBaseline { _ -> emptySet() } - - fun DiktatProcessorListener.skipKnownErrors(baseline: DiktatBaseline): DiktatProcessorListener = object : DiktatProcessorListener { - override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) { - if (!baseline.errorsByFile(file).contains(error)) { - this@skipKnownErrors.onError(file, error, isCorrected) - } - } - - override fun beforeAll() = this@skipKnownErrors.beforeAll() - override fun before(file: Path) = this@skipKnownErrors.before(file) - override fun after(file: Path) = this@skipKnownErrors.after(file) - override fun afterAll() = this@skipKnownErrors.afterAll() - } - } -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt deleted file mode 100644 index 9276e0ae38..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.cqfn.diktat.api - -import java.nio.file.Path - -/** - * A factory to load or generate [DiktatBaseline] - */ -interface DiktatBaselineFactory { - /** - * @param baselineFile - * @param sourceRootDir a dir to detect relative path for processing files - * @return Loaded [DiktatBaseline] from [baselineFile] or null if it gets an error in loading - */ - fun tryToLoad( - baselineFile: Path, - sourceRootDir: Path, - ): DiktatBaseline? - - /** - * @param baselineFile - * @param sourceRootDir a dir to detect relative path for processing files - * @return [DiktatProcessorListener] which generates baseline in [baselineFile] - */ - fun generator( - baselineFile: Path, - sourceRootDir: Path, - ): DiktatProcessorListener -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt deleted file mode 100644 index de6ea06de7..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.cqfn.diktat.api - -/** - * Callback for diktat process - */ -@FunctionalInterface -fun interface DiktatCallback : Function2 { - /** - * Performs this callback on the given [error] taking into account [isCorrected] flag. - * - * @param error the error found by diktat - * @param isCorrected true if the error fixed by diktat - */ - override fun invoke(error: DiktatError, isCorrected: Boolean) -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt deleted file mode 100644 index 3d4aac4758..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.cqfn.diktat.api - -/** - * Error found by `diktat` - */ -interface DiktatError { - /** - * @return line number (one-based) - */ - fun getLine(): Int - - /** - * @return column number (one-based) - */ - fun getCol(): Int - - /** - * @return rule id - */ - fun getRuleId(): String - - /** - * @return error message - */ - fun getDetail(): String - - /** - * @return true if the found error can be fixed - */ - fun canBeAutoCorrected(): Boolean -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt deleted file mode 100644 index 4ddd077357..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.cqfn.diktat.api - - -/** - * The **file-specific** error emitter, initialized and used in [DiktatRule] implementations. - * - * Since the file is indirectly a part of the state of a `DiktatRule`, the same - * `DiktatRule` instance should **never be re-used** to check more than a single - * file, or confusing effects (incl. race conditions) will occur. - * - * @see DiktatRule - */ -fun interface DiktatErrorEmitter : Function3 { - /** - * @param offset - * @param errorMessage - * @param canBeAutoCorrected - */ - override fun invoke(offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt deleted file mode 100644 index 513019f8d8..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt +++ /dev/null @@ -1,86 +0,0 @@ -package org.cqfn.diktat.api - -import java.nio.file.Path -import java.util.concurrent.atomic.AtomicInteger - -/** - * A listener for [org.cqfn.diktat.DiktatProcessor] - */ -interface DiktatProcessorListener { - /** - * Called once, before [org.cqfn.diktat.DiktatProcessor] starts process a bunch of files. - */ - fun beforeAll(): Unit = Unit - - /** - * Called before each file when [org.cqfn.diktat.DiktatProcessor] starts to process it. - * - * @param file - */ - fun before(file: Path): Unit = Unit - - /** - * Called on each error when [org.cqfn.diktat.DiktatProcessor] detects such one. - * - * @param file - * @param error - * @param isCorrected - */ - fun onError(file: Path, error: DiktatError, isCorrected: Boolean) - - /** - * Called after each file when [org.cqfn.diktat.DiktatProcessor] finished to process it. - * - * @param file - */ - fun after(file: Path): Unit = Unit - - /** - * Called once, after the processing of [org.cqfn.diktat.DiktatProcessor] finished. - */ - fun afterAll(): Unit = Unit - - companion object { - /** - * An empty implementation of [DiktatProcessorListener] - */ - open class Empty : DiktatProcessorListener { - override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = Unit - } - - /** - * An instance of [DiktatProcessorListener.Empty] - */ - val empty = Empty() - - /** - * @param listeners - * @return a single [DiktatProcessorListener] which uses all provided [listeners] - */ - operator fun invoke(vararg listeners: DiktatProcessorListener): DiktatProcessorListener = object : DiktatProcessorListener { - override fun beforeAll() = listeners.forEach(DiktatProcessorListener::beforeAll) - override fun before(file: Path) = listeners.forEach { it.before(file) } - override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = listeners.forEach { it.onError(file, error, isCorrected) } - override fun after(file: Path) = listeners.forEach { it.after(file) } - override fun afterAll() = listeners.forEach(DiktatProcessorListener::afterAll) - } - - /** - * An implementation of [DiktatProcessorListener] which counts [DiktatError]s - */ - fun AtomicInteger.countErrorsAsProcessorListener(): DiktatProcessorListener = object : DiktatProcessorListener { - override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) { - incrementAndGet() - } - } - - /** - * An implementation of [DiktatProcessorListener] which closes [AutoCloseable] at the end - */ - fun AutoCloseable.closeAfterAllAsProcessorListener(): DiktatProcessorListener = object : Empty() { - override fun afterAll() { - this@closeAfterAllAsProcessorListener.close() - } - } - } -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt deleted file mode 100644 index 55f054f3ed..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.cqfn.diktat.api - -import java.io.OutputStream -import java.nio.file.Path - -typealias DiktatReporter = DiktatProcessorListener - -/** - * A factory to create [DiktatReporter] - */ -interface DiktatReporterFactory : Function3 { - /** - * Set of supported IDs - */ - val ids: Set - - /** - * ID of [DiktatReporter] for plain output - */ - val plainId: String - - /** - * Names of color for plain output - */ - val colorNamesInPlain: Set - - /** - * @param id ID of [DiktatReporter] - * @param outputStream - * @param sourceRootDir a dir to detect relative path for processing files - * @return created [DiktatReporter] - */ - override operator fun invoke( - id: String, - outputStream: OutputStream, - sourceRootDir: Path, - ): DiktatReporter - - /** - * @param outputStream - * @param sourceRootDir a dir to detect relative path for processing files - * @param colorName name of color for colorful output, `null` means to disable colorization. - * @param groupByFile - * @return [DiktatReporter] for plain output - */ - fun createPlain( - outputStream: OutputStream, - sourceRootDir: Path, - colorName: String? = null, - groupByFile: Boolean = false, - ): DiktatReporter -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt deleted file mode 100644 index c63e0dfba7..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.cqfn.diktat.api - -import org.jetbrains.kotlin.com.intellij.lang.ASTNode - -/** -* This is a base interface for diktat's rules -*/ -interface DiktatRule : Function3 { - /** - * A unique ID of this rule. - */ - val id: String - - /** - * This method is going to be executed for each node in AST (in DFS fashion). - * - * @param node AST node - * @param autoCorrect indicates whether rule should attempt autocorrection - * @param emitter a way for rule to notify about a violation (lint error) - */ - override fun invoke( - node: ASTNode, - autoCorrect: Boolean, - emitter: DiktatErrorEmitter, - ) -} diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt deleted file mode 100644 index 18700f5a24..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.cqfn.diktat.api - -/** - * A group of [DiktatRule]'s as a single set. - * - * @property rules diktat rules. - */ -data class DiktatRuleSet( - val rules: List -) diff --git a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt b/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt deleted file mode 100644 index 5f66e87e64..0000000000 --- a/diktat-runner/diktat-runner-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.cqfn.diktat.api - -import java.nio.file.Path -import kotlin.io.path.absolutePathString - -/** - * A factory which creates a [DiktatRuleSet]. - */ -interface DiktatRuleSetFactory : Function0 { - /** - * @return the default instance of [DiktatRuleSet] - */ - override operator fun invoke(): DiktatRuleSet - - /** - * @param configFile a path to file with configuration for diktat (`diktat-analysis.yml`) - * @return created [DiktatRuleSet] using [configFile] - */ - fun create(configFile: String): DiktatRuleSet - - /** - * @param configFile a file with configuration for diktat (`diktat-analysis.yml`) - * @return created [DiktatRuleSet] using [configFile] - */ - fun create(configFile: Path): DiktatRuleSet = create(configFile.absolutePathString()) -} diff --git a/diktat-runner/diktat-runner-cli/build.gradle.kts b/diktat-runner/diktat-runner-cli/build.gradle.kts deleted file mode 100644 index a85a2973bc..0000000000 --- a/diktat-runner/diktat-runner-cli/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -@Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 -plugins { - id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") - id("org.cqfn.diktat.buildutils.code-quality-convention") - id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") - alias(libs.plugins.kotlin.plugin.serialization) -} - -project.description = "This module builds diktat-runner implementation using ktlint as CLI" - -dependencies { - api(projects.diktatRunner.diktatRunnerApi) - implementation(projects.diktatRules) - implementation(projects.diktatRunner.diktatRunnerKtlintEngine) - implementation(libs.kotlinx.cli) - implementation(libs.log4j2.core) - implementation(libs.ktlint.reporter.baseline) - implementation(libs.ktlint.reporter.checkstyle) - implementation(libs.ktlint.reporter.html) - implementation(libs.ktlint.reporter.json) - implementation(libs.ktlint.reporter.plain) - implementation(libs.ktlint.reporter.sarif) -} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt deleted file mode 100644 index 780c3ff04f..0000000000 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ /dev/null @@ -1,76 +0,0 @@ -/** - * The file contains main method - */ - -package org.cqfn.diktat - -import org.cqfn.diktat.cli.DiktatMode -import org.cqfn.diktat.api.DiktatProcessorListener -import org.cqfn.diktat.cli.DiktatProperties -import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl -import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl -import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript -import org.cqfn.diktat.util.tryToPathIfExists -import org.cqfn.diktat.util.walkByGlob -import mu.KotlinLogging -import java.nio.charset.StandardCharsets -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.readText -import kotlin.io.path.writeText - -private val log = KotlinLogging.logger { } - -@Suppress( - "LongMethod", - "TOO_LONG_FUNCTION" -) -fun main(args: Array) { - val diktatReporterFactory = DiktatReporterFactoryImpl() - val properties = DiktatProperties.parse(diktatReporterFactory, args) - properties.configureLogger() - - log.debug { - "Loading diktatRuleSet using config ${properties.config}" - } - val diktatRuleSet = DiktatRuleSetFactoryImpl().create(properties.config) - val diktatProcessor = DiktatProcessorFactoryImpl().invoke(diktatRuleSet) - val currentFolder = Paths.get(".") - val reporter = properties.reporter(diktatReporterFactory, currentFolder) - - log.debug { - "Resolving files by patterns: ${properties.patterns}" - } - val files = properties.patterns - .asSequence() - .flatMap { pattern -> - pattern.tryToPathIfExists()?.let { sequenceOf(it) } - ?: currentFolder.walkByGlob(pattern) - } - .filter { file -> file.isKotlinCodeOrScript() } - .distinct() - .map { it.normalize() } - - val loggingListener = object : DiktatProcessorListener.Companion.Empty() { - override fun before(file: Path) { - log.debug { - "Start processing the file: $file" - } - } - } - when (properties.mode) { - DiktatMode.CHECK -> diktatProcessor.checkAll( - listener = DiktatProcessorListener(loggingListener, reporter), - files = files, - ) - DiktatMode.FIX -> diktatProcessor.fixAll( - listener = DiktatProcessorListener(loggingListener, reporter), - files = files, - ) { file, formatterText -> - if (file.readText(StandardCharsets.UTF_8) != formatterText) { - file.writeText(formatterText, Charsets.UTF_8) - } - } - } -} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt deleted file mode 100644 index 1618e008f4..0000000000 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.cqfn.diktat.cli - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -/** - * Mode of `diktat` - */ -@Serializable -enum class DiktatMode { - @SerialName("check") - CHECK, - @SerialName("fix") - FIX, - ; -} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt deleted file mode 100644 index 593880e368..0000000000 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ /dev/null @@ -1,225 +0,0 @@ -package org.cqfn.diktat.cli - -import org.cqfn.diktat.api.DiktatReporter -import org.cqfn.diktat.api.DiktatReporterFactory -import org.cqfn.diktat.common.config.rules.DIKTAT -import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF -import com.pinterest.ktlint.core.Reporter -import com.pinterest.ktlint.core.ReporterProvider -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.core.LoggerContext -import org.slf4j.event.Level -import java.io.PrintStream -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.createDirectories -import kotlin.io.path.outputStream -import kotlin.system.exitProcess -import kotlinx.cli.ArgParser -import kotlinx.cli.ArgType -import kotlinx.cli.default -import kotlinx.cli.vararg - -/** - * @property config path to `diktat-analysis.yml` - * @property mode mode of `diktat` - * @property reporterProviderId - * @property output - * @property patterns - */ -data class DiktatProperties( - val config: String, - val mode: DiktatMode, - val reporterProviderId: String, - val output: String?, - private val groupByFileInPlain: Boolean, - private val colorNameInPlain: String?, - private val logLevel: Level, - val patterns: List, -) { - /** - * @param diktatReporterFactory - * @param sourceRootDir - * @return a configured [Reporter] - */ - fun reporter( - diktatReporterFactory: DiktatReporterFactory, - sourceRootDir: Path, - ): DiktatReporter { - val outputStream = output - ?.let { Paths.get(it) } - ?.also { it.parent.createDirectories() } - ?.outputStream() - ?.let { PrintStream(it) } - ?: System.out - return if (reporterProviderId == diktatReporterFactory.plainId) { - diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) - } else { - require(colorNameInPlain == null) { - "colorization is applicable only for plain reporter" - } - require(!groupByFileInPlain) { - "groupByFile is applicable only for plain reporter" - } - diktatReporterFactory.invoke(reporterProviderId, outputStream, sourceRootDir) - } - } - - /** - * Configure logger level using [logLevel] - */ - fun configureLogger() { - // set log level - LogManager.getContext(false) - .let { it as LoggerContext } - .also { ctx -> - ctx.configuration.rootLogger.level = when (logLevel) { - Level.ERROR -> org.apache.logging.log4j.Level.ERROR - Level.WARN -> org.apache.logging.log4j.Level.WARN - Level.INFO -> org.apache.logging.log4j.Level.INFO - Level.DEBUG -> org.apache.logging.log4j.Level.DEBUG - Level.TRACE -> org.apache.logging.log4j.Level.TRACE - } - } - .updateLoggers() - } - - companion object { - /** - * @param diktatReporterFactory - * @param args cli arguments - * @return parsed [DiktatProperties] - */ - @Suppress( - "LongMethod", - "TOO_LONG_FUNCTION" - ) - fun parse( - diktatReporterFactory: DiktatReporterFactory, - args: Array, - ): DiktatProperties { - val parser = ArgParser(DIKTAT) - val config: String by parser.option( - type = ArgType.String, - fullName = "config", - shortName = "c", - description = "Specify the location of the YAML configuration file. By default, $DIKTAT_ANALYSIS_CONF in the current directory is used.", - ).default(DIKTAT_ANALYSIS_CONF) - val mode: DiktatMode by parser.option( - type = ArgType.Choice(), - fullName = "mode", - shortName = "m", - description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." - ).default(DiktatMode.CHECK) - val reporterProviderId: String by parser.reporterProviderId(diktatReporterFactory) - val output: String? by parser.option( - type = ArgType.String, - fullName = "output", - shortName = "o", - description = "Redirect the reporter output to a file.", - ) - val groupByFileInPlain: Boolean by parser.option( - type = ArgType.Boolean, - fullName = "plain-group-by-file", - shortName = null, - description = "A flag for plain reporter" - ).default(false) - val colorName: String? by parser.colorName(diktatReporterFactory) - val logLevel: Level by parser.option( - type = ArgType.Choice(), - fullName = "log-level", - shortName = "l", - description = "Enable the output with specific level", - ).default(Level.INFO) - val patterns: List by parser.argument( - type = ArgType.String, - description = "A list of files to process by diktat" - ).vararg() - - parser.addOptionAndShowResourceWithExit( - fullName = "version", - shortName = "V", - description = "Output version information and exit.", - args = args, - resourceName = "META-INF/diktat/version" - ) - parser.addOptionAndShowResourceWithExit( - fullName = "license", - shortName = null, - description = "Display the license and exit.", - args = args, - resourceName = "META-INF/diktat/LICENSE", - ) - - parser.parse(args) - return DiktatProperties( - config = config, - mode = mode, - reporterProviderId = reporterProviderId, - output = output, - groupByFileInPlain = groupByFileInPlain, - colorNameInPlain = colorName, - logLevel = logLevel, - patterns = patterns, - ) - } - - /** - * @param diktatReporterFactory - * @return a single [ReporterProvider] as parsed cli arg - */ - private fun ArgParser.reporterProviderId(diktatReporterFactory: DiktatReporterFactory) = option( - type = ArgType.Choice( - choices = diktatReporterFactory.ids.toList(), - toVariant = { it }, - variantToString = { it }, - ), - fullName = "reporter", - shortName = "r", - description = "The reporter to use" - ) - .default(diktatReporterFactory.plainId) - - /** - * @param diktatReporterFactory - * @return a single and optional color name as parsed cli args - */ - private fun ArgParser.colorName(diktatReporterFactory: DiktatReporterFactory) = this.option( - type = ArgType.Choice( - choices = diktatReporterFactory.colorNamesInPlain.toList(), - toVariant = { it }, - variantToString = { it }, - ), - fullName = "plain-color", - shortName = null, - description = "Colorize the output.", - ) - - private fun ArgParser.addOptionAndShowResourceWithExit( - fullName: String, - shortName: String?, - description: String, - args: Array, - resourceName: String, - ) { - // add here to print in help - option( - type = ArgType.Boolean, - fullName = fullName, - shortName = shortName, - description = description - ) - if (args.contains("--$fullName") || shortName?.let { args.contains("-$it") } == true) { - @Suppress("DEBUG_PRINT", "ForbiddenMethodCall") - print(readFromResource(resourceName)) - exitProcess(0) - } - } - - private fun readFromResource(resourceName: String): String = DiktatProperties::class.java - .classLoader - .getResource(resourceName) - ?.readText() - ?: error("Resource $resourceName not found") - } -} diff --git a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt b/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt deleted file mode 100644 index 22f5027272..0000000000 --- a/diktat-runner/diktat-runner-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This class contains util methods to operate with java.nio.file.Path - */ - -package org.cqfn.diktat.util - -import java.nio.file.InvalidPathException -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.PathWalkOption -import kotlin.io.path.exists -import kotlin.io.path.walk - -/** - * Create a matcher and return a filter that uses it. - * - * @param glob glob pattern to filter files - * @return a sequence of files which matches to [glob] - */ -@OptIn(ExperimentalPathApi::class) -fun Path.walkByGlob(glob: String): Sequence = - fileSystem.getPathMatcher("glob:$glob") - .let { matcher -> - this.walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { matcher.matches(it) } - } - -/** - * @return path or null if path is invalid or doesn't exist - */ -fun String.tryToPathIfExists(): Path? = try { - Paths.get(this).takeIf { it.exists() } -} catch (e: InvalidPathException) { - null -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts b/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts deleted file mode 100644 index a12bdc533d..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") - id("org.cqfn.diktat.buildutils.code-quality-convention") - id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") -} - -project.description = "This module builds diktat-runner implementation using ktlint" - -dependencies { - api(projects.diktatRunner.diktatRunnerApi) - implementation(projects.diktatCommon) - implementation(libs.ktlint.core) - implementation(libs.ktlint.reporter.baseline) - implementation(libs.ktlint.reporter.checkstyle) - implementation(libs.ktlint.reporter.html) - implementation(libs.ktlint.reporter.json) - implementation(libs.ktlint.reporter.plain) - implementation(libs.ktlint.reporter.sarif) - - testImplementation(libs.junit.jupiter) - testImplementation(libs.junit.platform.suite) - testImplementation(libs.assertj.core) - testImplementation(libs.mockito) -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt deleted file mode 100644 index 71391e8923..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Utilities related to logging - */ - -package org.cqfn.diktat.common.utils - -import com.pinterest.ktlint.core.initKtLintKLogger -import mu.KotlinLogging -import kotlin.reflect.KClass - -/** - * Create a logger using [KotlinLogging] and configure it by ktlint's mechanism - * - * @param func empty fun which is used to get enclosing class name - * @return a logger - */ -fun KotlinLogging.loggerWithKtlintConfig(func: () -> Unit) = - logger(func).initKtLintKLogger() - -/** - * Create a logger using [KotlinLogging] and configure it by ktlint's mechanism - * - * @param clazz a class for which logger is needed - * @return a logger - */ -fun KotlinLogging.loggerWithKtlintConfig(clazz: KClass<*>) = - logger(clazz.java.name).initKtLintKLogger() diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt deleted file mode 100644 index ae04bbb8c9..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatBaseline -import org.cqfn.diktat.api.DiktatBaselineFactory -import org.cqfn.diktat.api.DiktatProcessorListener -import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener -import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap -import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap -import com.pinterest.ktlint.core.internal.loadBaseline -import com.pinterest.ktlint.reporter.baseline.BaselineReporter -import java.io.PrintStream -import java.nio.file.Path -import kotlin.io.path.absolutePathString -import kotlin.io.path.outputStream - -/** - * A factory to create or generate [DiktatBaseline] using `KtLint` - */ -@Suppress("DEPRECATION") -class DiktatBaselineFactoryImpl : DiktatBaselineFactory { - override fun tryToLoad(baselineFile: Path, sourceRootDir: Path): DiktatBaseline? { - return loadBaseline(baselineFile.absolutePathString()) - .takeUnless { it.baselineGenerationNeeded } - ?.let { ktLintBaseline -> - DiktatBaseline { file -> - ktLintBaseline.baselineRules - ?.get(file.relativePathStringTo(sourceRootDir)) - .orEmpty() - .map { it.wrap() } - .toSet() - } - } - } - - override fun generator(baselineFile: Path, sourceRootDir: Path): DiktatProcessorListener { - val outputStream = baselineFile.outputStream() - return DiktatProcessorListener( - BaselineReporter(PrintStream(outputStream)).wrap(sourceRootDir), - outputStream.closeAfterAllAsProcessorListener() - ) - } -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt deleted file mode 100644 index 4b309ad50f..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatError -import com.pinterest.ktlint.core.LintError - -/** - * Wrapper for KtLint error - * - * @property lintError - */ -data class DiktatErrorImpl( - private val lintError: LintError -) : DiktatError { - override fun getLine(): Int = lintError.line - - override fun getCol(): Int = lintError.col - - override fun getRuleId(): String = lintError.ruleId - - override fun getDetail(): String = lintError.detail - - override fun canBeAutoCorrected(): Boolean = lintError.canBeAutoCorrected - - companion object { - /** - * @return [DiktatError] from KtLint [LintError] - */ - fun LintError.wrap(): DiktatError = DiktatErrorImpl(this) - - /** - * @return KtLint [LintError] from [DiktatError] or exception - */ - fun DiktatError.unwrap(): LintError = (this as? DiktatErrorImpl)?.lintError - ?: error("Unsupported wrapper of ${DiktatError::class.java.simpleName}: ${this::class.java.canonicalName}") - } -} - diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt deleted file mode 100644 index fca4f39938..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.DiktatProcessor -import org.cqfn.diktat.DiktatProcessorFactory -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.api.DiktatRuleSet -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.api.EditorConfigOverride -import java.nio.charset.StandardCharsets -import java.nio.file.Path -import kotlin.io.path.absolutePathString -import kotlin.io.path.readText - -/** - * A factory to create [DiktatProcessor] using [DiktatProcessorFactory] and `KtLint` as engine - */ -class DiktatProcessorFactoryImpl : DiktatProcessorFactory { - override fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor = object : DiktatProcessor() { - override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(ktLintParams(diktatRuleSet, file, callback.unwrap())) - override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(ktLintParams(diktatRuleSet, file, callback.unwrap())) - } - - @Suppress("DEPRECATION") - private fun ktLintParams( - diktatRuleSet: DiktatRuleSet, - file: Path, - callback: LintErrorCallback, - ): KtLint.ExperimentalParams = KtLint.ExperimentalParams( - fileName = file.absolutePathString(), - text = file.readText(StandardCharsets.UTF_8), - ruleSets = setOf(diktatRuleSet.toKtLint()), - userData = emptyMap(), - cb = callback, - script = false, // internal API of KtLint - editorConfigPath = null, - debug = false, // we do not use it - editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride, - isInvokedFromCli = false - ) -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt deleted file mode 100644 index 8fbb804a43..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ /dev/null @@ -1,81 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatReporter -import org.cqfn.diktat.api.DiktatReporterFactory -import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap -import com.pinterest.ktlint.core.ReporterProvider -import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider -import com.pinterest.ktlint.reporter.html.HtmlReporterProvider -import com.pinterest.ktlint.reporter.json.JsonReporterProvider -import com.pinterest.ktlint.reporter.plain.PlainReporterProvider -import com.pinterest.ktlint.reporter.plain.internal.Color -import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider -import java.io.OutputStream -import java.io.PrintStream -import java.nio.file.Path -import kotlin.io.path.pathString - -/** - * A factory to create [DiktatReporter] using `KtLint` - */ -class DiktatReporterFactoryImpl : DiktatReporterFactory { - private val plainReporterProvider = PlainReporterProvider() - - /** - * All [ReporterProvider] which __KtLint__ provides - */ - private val reporterProviders = setOf( - plainReporterProvider, - JsonReporterProvider(), - SarifReporterProvider(), - CheckStyleReporterProvider(), - HtmlReporterProvider(), - ) - .associateBy { it.id } - - override val ids: Set - get() = reporterProviders.keys - - override val plainId: String - get() = plainReporterProvider.id - - override val colorNamesInPlain: Set - get() = Color.values().map { it.name }.toSet() - - override fun invoke( - id: String, - outputStream: OutputStream, - sourceRootDir: Path, - ): DiktatReporter { - val reporterProvider = reporterProviders[id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") - if (reporterProvider is SarifReporterProvider) { - System.setProperty("user.home", sourceRootDir.pathString) - } - val opt = if (reporterProvider is PlainReporterProvider) { - mapOf("color" to "DARK_GRAY") - } else { - emptyMap() - } - return reporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) - } - - override fun createPlain(outputStream: OutputStream, sourceRootDir: Path, colorName: String?, groupByFile: Boolean): DiktatReporter { - val opt = buildMap { - colorName?.let { - put("color", true) - put("color_name", it) - } ?: run { - put("color", false) - put("color_name", Color.DARK_GRAY) - } - if (groupByFile) { - put("group_by_file", true) - } - }.mapValues { it.value.toString() } - return plainReporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) - } - - companion object { - private fun OutputStream.asPrintStream(): PrintStream = (this as? PrintStream) ?: PrintStream(this) - } -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt deleted file mode 100644 index a6e1b437d0..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatError -import org.cqfn.diktat.api.DiktatReporter -import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap -import com.pinterest.ktlint.core.Reporter -import java.nio.file.Path - -/** - * [DiktatReporter] using __KtLint__ - */ -class DiktatReporterImpl( - private val ktLintReporter: Reporter, - private val sourceRootDir: Path, -) : DiktatReporter { - override fun beforeAll(): Unit = ktLintReporter.beforeAll() - override fun before(file: Path): Unit = ktLintReporter.before(file.relativePathStringTo(sourceRootDir)) - override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = ktLintReporter.onLintError(file.relativePathStringTo(sourceRootDir), error.unwrap(), isCorrected) - override fun after(file: Path): Unit = ktLintReporter.after(file.relativePathStringTo(sourceRootDir)) - override fun afterAll(): Unit = ktLintReporter.beforeAll() - - companion object { - /** - * @param sourceRootDir - * @return [DiktatReporter] which wraps __KtLint__'s [Reporter] - */ - fun Reporter.wrap(sourceRootDir: Path): DiktatReporter = DiktatReporterImpl(this, sourceRootDir) - - /** - * @return __KtLint__'s [Reporter] - */ - internal fun DiktatReporter.unwrap(): Reporter = (this as? DiktatReporterImpl)?.ktLintReporter - ?: error("Unsupported wrapper of ${DiktatReporter::class.java.simpleName}: ${this::class.java.canonicalName}") - } -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt deleted file mode 100644 index 46bf92c8f9..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatRule -import org.cqfn.diktat.api.DiktatRuleSet -import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet - -/** - * This is a wrapper around __KtLint__'s [RuleSet] which adjusts visitorModifiers for all rules to keep order with prevRule - * Added as a workaround after introducing a new logic for sorting KtLint Rules: https://github.com/pinterest/ktlint/issues/1478 - * - * @param diktatRuleSet the rules which belong to the current [DiktatRuleSet]. - */ -class KtLintRuleSetWrapper private constructor( - diktatRuleSet: DiktatRuleSet, -) : RuleSet(DIKTAT_RULE_SET_ID, rules = wrapRules(diktatRuleSet.rules)) { - companion object { - /** - * @return __KtLint__'s [RuleSet] created from [DiktatRuleSet] - */ - fun DiktatRuleSet.toKtLint(): RuleSet = KtLintRuleSetWrapper(this) - - private fun wrapRules(rules: List): Array { - if (rules.isEmpty()) { - return emptyArray() - } - return rules.runningFold(null as KtLintRuleWrapper?) { prevRule, diktatRule -> - KtLintRuleWrapper(diktatRule, prevRule) - } - .filterNotNull() - .toTypedArray() - } - } -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt deleted file mode 100644 index 19d3e7716a..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatRule -import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID -import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId -import com.pinterest.ktlint.core.Rule -import org.jetbrains.kotlin.com.intellij.lang.ASTNode - -private typealias EmitType = (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit - -/** - * This is a wrapper around __KtLint__'s [Rule] which adjusts visitorModifiers to keep order with prevRule. - * @property rule - */ -class KtLintRuleWrapper( - val rule: DiktatRule, - prevRule: KtLintRuleWrapper? = null, -) : Rule( - id = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID), - visitorModifiers = createVisitorModifiers(rule, prevRule), -) { - @Deprecated( - "Marked for deletion in ktlint 0.48.0", - replaceWith = ReplaceWith("beforeVisitChildNodes(node, autoCorrect, emit)"), - ) - override fun visit( - node: ASTNode, - autoCorrect: Boolean, - emit: EmitType, - ) = rule.invoke(node, autoCorrect, emit) - - companion object { - private fun createVisitorModifiers( - rule: DiktatRule, - prevRule: KtLintRuleWrapper?, - ): Set = prevRule?.id?.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID) - ?.let { previousRuleId -> - val ruleId = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID) - require(ruleId != previousRuleId) { - "PrevRule has same ID as rule: $ruleId" - } - setOf( - VisitorModifier.RunAfterRule( - ruleId = previousRuleId, - loadOnlyWhenOtherRuleIsLoaded = false, - runOnlyWhenOtherRuleIsEnabled = false - ) - ) - } ?: emptySet() - - /** - * @return a rule to which a logic is delegated - */ - internal fun Rule.unwrap(): DiktatRule = (this as? KtLintRuleWrapper)?.rule ?: error("Provided rule ${javaClass.simpleName} is not wrapped by diktat") - } -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt b/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt deleted file mode 100644 index cff1ce20b3..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt +++ /dev/null @@ -1,96 +0,0 @@ -/** - * This file contains util methods for __KtLint__ - */ - -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatCallback -import org.cqfn.diktat.api.DiktatRuleSet -import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap -import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.KtLint.ExperimentalParams -import com.pinterest.ktlint.core.LintError -import mu.KotlinLogging -import org.intellij.lang.annotations.Language -import java.nio.file.Path -import kotlin.io.path.invariantSeparatorsPathString -import kotlin.io.path.relativeTo - -typealias LintErrorCallback = (LintError, Boolean) -> Unit - -private val log = KotlinLogging.logger { } - -val defaultCallback = DiktatCallback { error, _ -> - log.warn { "Received linting error: $error" } -} - -/** - * @param sourceRootDir - * @return relative path to [sourceRootDir] as [String] - */ -fun Path.relativePathStringTo(sourceRootDir: Path): String = relativeTo(sourceRootDir).invariantSeparatorsPathString - -/** - * @return [DiktatCallback] from KtLint [LintErrorCallback] - */ -fun LintErrorCallback.wrap(): DiktatCallback = DiktatCallback { error, isCorrected -> - this(error.unwrap(), isCorrected) -} - -/** - * @return KtLint [LintErrorCallback] from [DiktatCallback] or exception - */ -fun DiktatCallback.unwrap(): LintErrorCallback = { error, isCorrected -> - this(error.wrap(), isCorrected) -} - -/** - * Enables ignoring autocorrected errors when in "fix" mode (i.e. when - * [KtLint.format] is invoked). - * - * Before version 0.47, _Ktlint_ only reported non-corrected errors in "fix" - * mode. - * Now, this has changed. - * - * @receiver the instance of _Ktlint_ parameters. - * @return the instance with the [callback][ExperimentalParams.cb] modified in - * such a way that it ignores corrected errors. - * @see KtLint.format - * @see ExperimentalParams.cb - * @since 1.2.4 - */ -private fun ExperimentalParams.ignoreCorrectedErrors(): ExperimentalParams = - copy(cb = { error: LintError, corrected: Boolean -> - if (!corrected) { - cb(error, false) - } - }) - -/** - * @param ruleSetSupplier - * @param text - * @param fileName - * @param cb callback to be called on unhandled [LintError]s - * @return formatted code - */ -@Suppress("LAMBDA_IS_NOT_LAST_PARAMETER") -fun format( - ruleSetSupplier: () -> DiktatRuleSet, - @Language("kotlin") text: String, - fileName: String, - cb: DiktatCallback = defaultCallback -): String { - val ruleSets = listOf(ruleSetSupplier().toKtLint()) - return KtLint.format( - ExperimentalParams( - text = text, - ruleSets = ruleSets, - fileName = fileName.removeSuffix("_copy"), - script = fileName.removeSuffix("_copy").endsWith("kts"), - cb = cb.unwrap(), - debug = true, - ).ignoreCorrectedErrors() - ) -} diff --git a/diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt b/diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt deleted file mode 100644 index 3d5014391e..0000000000 --- a/diktat-runner/diktat-runner-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt +++ /dev/null @@ -1,161 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatErrorEmitter -import org.cqfn.diktat.api.DiktatRule -import org.cqfn.diktat.api.DiktatRuleSet -import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.unwrap -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.Rule -import org.assertj.core.api.Assertions.assertThat -import org.intellij.lang.annotations.Language -import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test - -class KtLintRuleSetWrapperTest { - @Test - fun `check KtLintRuleSetWrapper with duplicate`() { - val rule = mockRule("rule") - Assertions.assertThrows(IllegalArgumentException::class.java) { - DiktatRuleSet(listOf(rule, rule)).toKtLint() - } - } - - @Test - fun `check OrderedRule`() { - val ruleSetId = "id" - - val rule1 = mockRule(id = "rule-first".qualifiedWithRuleSetId(ruleSetId)) - val rule2 = mockRule(id = "rule-second".qualifiedWithRuleSetId(ruleSetId)) - - val orderedRuleSet = DiktatRuleSet(listOf(rule1, rule2)).toKtLint() - - val orderedRuleSetIterator = orderedRuleSet.rules.iterator() - val orderedRule1 = orderedRuleSetIterator.next() - val orderedRule2 = orderedRuleSetIterator.next() - Assertions.assertFalse(orderedRuleSetIterator.hasNext(), "Extra elements after ordering") - - Assertions.assertEquals(rule1, orderedRule1.unwrap(), "First rule is modified") - - orderedRule2.visitorModifiers - .filterIsInstance() - .also { - Assertions.assertEquals(1, it.size, - "Found invalid count of Rule.VisitorModifier.RunAfterRule") - } - .first() - .let { - Assertions.assertEquals(rule1.id.qualifiedWithRuleSetId(ruleSetId), it.ruleId, - "Invalid ruleId in Rule.VisitorModifier.RunAfterRule") - } - } - - @Test - @Suppress("TOO_LONG_FUNCTION") - fun `KtLint keeps order with RuleVisitorModifierRunAfterRule`() { - val actualRuleInvocationOrder: MutableList = mutableListOf() - val onVisit: (DiktatRule) -> Unit = { rule -> - actualRuleInvocationOrder += rule.id - } - val ruleSetId = "id" - val rules: List = sequenceOf("ccc", "bbb", "aaa").map { ruleId -> - mockRule( - id = ruleId.qualifiedWithRuleSetId(ruleSetId), - onVisit = onVisit - ) - }.toList() - assertThat(rules).isNotEmpty - - /* - * Make sure the rules are not sorted by id. - */ - val rulesOrderedById: List = rules.sortedBy(DiktatRule::id) - assertThat(rules).containsExactlyInAnyOrder(*rulesOrderedById.toTypedArray()) - assertThat(rules).isNotEqualTo(rulesOrderedById) - - /* - * Make sure OrderedRuleSet preserves the order. - */ - val ruleSet = DiktatRuleSet(rules).toKtLint() - assertThat(ruleSet.rules.map(Rule::id)).containsExactlyElementsOf(rules.map(DiktatRule::id)) - - @Language("kotlin") - val code = "fun foo() { }" - - KtLint.lint( - KtLint.ExperimentalParams( - fileName = "TestFileName.kt", - text = code, - ruleSets = listOf(ruleSet), - cb = { _, _ -> }, - ) - ) - - val ruleCount = rules.size - assertThat(actualRuleInvocationOrder) - .describedAs("The ordered list of rule invocations") - .matches({ order -> - order.size % ruleCount == 0 - }, "has a size which is multiple of $ruleCount") - - /* - * This is the count of AST nodes in `code` above. - */ - val astNodeCount = actualRuleInvocationOrder.size / ruleCount - - /*- - * This is new in ktlint 0.47. - * Previously, rules were applied in this sequence: - * - * A -> B -> C (File) - * | - * V - * A -> B -> C (Node) - * | - * V - * A -> B -> C (Leaf) - * - * Now, each rule is recursively applied to all AST nodes, and then the - * control is passed to the next rule: - * - * A(File) -> A(Node) -> A(Leaf) - * | - * V - * B(File) -> B(Node) -> B(Leaf) - * | - * V - * C(File) -> C(Node) -> C(Leaf) - * - * val expectedRuleInvocationOrder = rules.asSequence() - * .map(Rule::id) - * .flatMap { ruleId -> - * generateSequence { ruleId }.take(astNodeCount) - * } - * .toList() - */ - val expectedRuleInvocationOrder = generateSequence { - rules.map(DiktatRule::id) - } - .take(astNodeCount) - .flatten() - .toList() - - assertThat(actualRuleInvocationOrder) - .containsExactlyElementsOf(expectedRuleInvocationOrder) - } - - companion object { - private fun mockRule( - id: String, - onVisit: (DiktatRule) -> Unit = { } - ): DiktatRule = object : DiktatRule { - override val id: String - get() = id - override fun invoke(node: ASTNode, autoCorrect: Boolean, emitter: DiktatErrorEmitter) { - onVisit(this) - } - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9f6c6db5ed..f6250cec8b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,6 +32,7 @@ plugins { } includeBuild("gradle/plugins") +include("diktat-api") include("diktat-common") include("diktat-gradle-plugin") include("diktat-maven-plugin") @@ -39,9 +40,8 @@ include("diktat-rules") include("diktat-ruleset") include("diktat-test-framework") include("diktat-dev-ksp") -include("diktat-runner:diktat-runner-api") -include("diktat-runner:diktat-runner-cli") -include("diktat-runner:diktat-runner-ktlint-engine") +include("diktat-cli") +include("diktat-ktlint-engine") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") From f0a1c3e8734b039f3d89693eb964dc07da902521 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 3 Apr 2023 18:58:33 +0300 Subject: [PATCH 11/34] fixed renaming --- diktat-api/build.gradle.kts | 11 + .../kotlin/org/cqfn/diktat/DiktatProcessor.kt | 72 ++++++ .../org/cqfn/diktat/DiktatProcessorFactory.kt | 15 ++ .../org/cqfn/diktat/api/DiktatBaseline.kt | 34 +++ .../cqfn/diktat/api/DiktatBaselineFactory.kt | 28 +++ .../org/cqfn/diktat/api/DiktatCallback.kt | 15 ++ .../kotlin/org/cqfn/diktat/api/DiktatError.kt | 31 +++ .../org/cqfn/diktat/api/DiktatErrorEmitter.kt | 20 ++ .../diktat/api/DiktatProcessorListener.kt | 86 +++++++ .../cqfn/diktat/api/DiktatReporterFactory.kt | 52 ++++ .../kotlin/org/cqfn/diktat/api/DiktatRule.kt | 26 ++ .../org/cqfn/diktat/api/DiktatRuleSet.kt | 10 + .../cqfn/diktat/api/DiktatRuleSetFactory.kt | 26 ++ diktat-cli/build.gradle.kts | 23 ++ .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 76 ++++++ .../kotlin/org/cqfn/diktat/cli/DiktatMode.kt | 16 ++ .../org/cqfn/diktat/cli/DiktatProperties.kt | 225 ++++++++++++++++++ .../kotlin/org/cqfn/diktat/util/FileUtils.kt | 36 +++ diktat-ktlint-engine/build.gradle.kts | 24 ++ .../cqfn/diktat/common/utils/LoggingUtils.kt | 27 +++ .../ktlint/DiktatBaselineFactoryImpl.kt | 42 ++++ .../org/cqfn/diktat/ktlint/DiktatErrorImpl.kt | 37 +++ .../ktlint/DiktatProcessorFactoryImpl.kt | 41 ++++ .../ktlint/DiktatReporterFactoryImpl.kt | 81 +++++++ .../cqfn/diktat/ktlint/DiktatReporterImpl.kt | 35 +++ .../diktat/ktlint/KtLintRuleSetWrapper.kt | 35 +++ .../cqfn/diktat/ktlint/KtLintRuleWrapper.kt | 56 +++++ .../org/cqfn/diktat/ktlint/KtLintUtils.kt | 96 ++++++++ .../diktat/ktlint/KtLintRuleSetWrapperTest.kt | 161 +++++++++++++ 29 files changed, 1437 insertions(+) create mode 100644 diktat-api/build.gradle.kts create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt create mode 100644 diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt create mode 100644 diktat-cli/build.gradle.kts create mode 100644 diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt create mode 100644 diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt create mode 100644 diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt create mode 100644 diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt create mode 100644 diktat-ktlint-engine/build.gradle.kts create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt create mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt create mode 100644 diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt diff --git a/diktat-api/build.gradle.kts b/diktat-api/build.gradle.kts new file mode 100644 index 0000000000..6877fdf899 --- /dev/null +++ b/diktat-api/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") + id("org.cqfn.diktat.buildutils.code-quality-convention") + id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") +} + +project.description = "This module builds diktat-runner-api" + +dependencies { + implementation(libs.kotlin.compiler.embeddable) +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt new file mode 100644 index 0000000000..af942c488a --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt @@ -0,0 +1,72 @@ +package org.cqfn.diktat + +import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.api.DiktatProcessorListener +import java.nio.file.Path + +/** + * Processor to run `diktat` + */ +abstract class DiktatProcessor { + /** + * Run `diktat fix` on provided [file] using [callback] for detected errors and returned formatted file content. + * + * @param file + * @param callback + * @return result of `diktat fix` + */ + abstract fun fix(file: Path, callback: DiktatCallback): String + + /** + * Run `diktat fix` for all [files] using [listener] during of processing and [formattedCodeHandler] to handle result of `diktat fix`. + * + * @param listener a listener which is called during processing. + * @param files + * @param formattedCodeHandler + */ + fun fixAll( + listener: DiktatProcessorListener = DiktatProcessorListener.empty, + files: Sequence, + formattedCodeHandler: (Path, String) -> Unit, + ) { + listener.beforeAll() + files.forEach { file -> + listener.before(file) + val formattedCode = fix(file) { error, isCorrected -> + listener.onError(file, error, isCorrected) + } + formattedCodeHandler(file, formattedCode) + listener.after(file) + } + listener.afterAll() + } + + /** + * Run `diktat check` on provided [file] using [callback] for detected errors. + * + * @param file + * @param callback + */ + abstract fun check(file: Path, callback: DiktatCallback) + + /** + * Run `diktat check` for all [files] using [listener] during of processing. + * + * @param listener a listener which is called during processing. + * @param files + */ + fun checkAll( + listener: DiktatProcessorListener = DiktatProcessorListener.empty, + files: Sequence, + ) { + listener.beforeAll() + files.forEach { file -> + listener.before(file) + check(file) { error, isCorrected -> + listener.onError(file, error, isCorrected) + } + listener.after(file) + } + listener.afterAll() + } +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt new file mode 100644 index 0000000000..8363826ed4 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessorFactory.kt @@ -0,0 +1,15 @@ +package org.cqfn.diktat + +import org.cqfn.diktat.api.DiktatRuleSet + +/** + * A factory to create [DiktatProcessor] using [DiktatRuleSet] + */ +@FunctionalInterface +interface DiktatProcessorFactory : Function1 { + /** + * @param diktatRuleSet + * @return created [DiktatProcessor] using [DiktatRuleSet] + */ + override operator fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt new file mode 100644 index 0000000000..1f7fbb9e1e --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt @@ -0,0 +1,34 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path + +/** + * A base interface for Baseline + */ +fun interface DiktatBaseline { + /** + * @param file + * @return a set of [DiktatError] found in baseline by [file] + */ + fun errorsByFile(file: Path): Set + + companion object { + /** + * Empty [DiktatBaseline] + */ + val empty: DiktatBaseline = DiktatBaseline { _ -> emptySet() } + + fun DiktatProcessorListener.skipKnownErrors(baseline: DiktatBaseline): DiktatProcessorListener = object : DiktatProcessorListener { + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) { + if (!baseline.errorsByFile(file).contains(error)) { + this@skipKnownErrors.onError(file, error, isCorrected) + } + } + + override fun beforeAll() = this@skipKnownErrors.beforeAll() + override fun before(file: Path) = this@skipKnownErrors.before(file) + override fun after(file: Path) = this@skipKnownErrors.after(file) + override fun afterAll() = this@skipKnownErrors.afterAll() + } + } +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt new file mode 100644 index 0000000000..9276e0ae38 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaselineFactory.kt @@ -0,0 +1,28 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path + +/** + * A factory to load or generate [DiktatBaseline] + */ +interface DiktatBaselineFactory { + /** + * @param baselineFile + * @param sourceRootDir a dir to detect relative path for processing files + * @return Loaded [DiktatBaseline] from [baselineFile] or null if it gets an error in loading + */ + fun tryToLoad( + baselineFile: Path, + sourceRootDir: Path, + ): DiktatBaseline? + + /** + * @param baselineFile + * @param sourceRootDir a dir to detect relative path for processing files + * @return [DiktatProcessorListener] which generates baseline in [baselineFile] + */ + fun generator( + baselineFile: Path, + sourceRootDir: Path, + ): DiktatProcessorListener +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt new file mode 100644 index 0000000000..de6ea06de7 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt @@ -0,0 +1,15 @@ +package org.cqfn.diktat.api + +/** + * Callback for diktat process + */ +@FunctionalInterface +fun interface DiktatCallback : Function2 { + /** + * Performs this callback on the given [error] taking into account [isCorrected] flag. + * + * @param error the error found by diktat + * @param isCorrected true if the error fixed by diktat + */ + override fun invoke(error: DiktatError, isCorrected: Boolean) +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt new file mode 100644 index 0000000000..3d4aac4758 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt @@ -0,0 +1,31 @@ +package org.cqfn.diktat.api + +/** + * Error found by `diktat` + */ +interface DiktatError { + /** + * @return line number (one-based) + */ + fun getLine(): Int + + /** + * @return column number (one-based) + */ + fun getCol(): Int + + /** + * @return rule id + */ + fun getRuleId(): String + + /** + * @return error message + */ + fun getDetail(): String + + /** + * @return true if the found error can be fixed + */ + fun canBeAutoCorrected(): Boolean +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt new file mode 100644 index 0000000000..4ddd077357 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatErrorEmitter.kt @@ -0,0 +1,20 @@ +package org.cqfn.diktat.api + + +/** + * The **file-specific** error emitter, initialized and used in [DiktatRule] implementations. + * + * Since the file is indirectly a part of the state of a `DiktatRule`, the same + * `DiktatRule` instance should **never be re-used** to check more than a single + * file, or confusing effects (incl. race conditions) will occur. + * + * @see DiktatRule + */ +fun interface DiktatErrorEmitter : Function3 { + /** + * @param offset + * @param errorMessage + * @param canBeAutoCorrected + */ + override fun invoke(offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt new file mode 100644 index 0000000000..513019f8d8 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatProcessorListener.kt @@ -0,0 +1,86 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path +import java.util.concurrent.atomic.AtomicInteger + +/** + * A listener for [org.cqfn.diktat.DiktatProcessor] + */ +interface DiktatProcessorListener { + /** + * Called once, before [org.cqfn.diktat.DiktatProcessor] starts process a bunch of files. + */ + fun beforeAll(): Unit = Unit + + /** + * Called before each file when [org.cqfn.diktat.DiktatProcessor] starts to process it. + * + * @param file + */ + fun before(file: Path): Unit = Unit + + /** + * Called on each error when [org.cqfn.diktat.DiktatProcessor] detects such one. + * + * @param file + * @param error + * @param isCorrected + */ + fun onError(file: Path, error: DiktatError, isCorrected: Boolean) + + /** + * Called after each file when [org.cqfn.diktat.DiktatProcessor] finished to process it. + * + * @param file + */ + fun after(file: Path): Unit = Unit + + /** + * Called once, after the processing of [org.cqfn.diktat.DiktatProcessor] finished. + */ + fun afterAll(): Unit = Unit + + companion object { + /** + * An empty implementation of [DiktatProcessorListener] + */ + open class Empty : DiktatProcessorListener { + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = Unit + } + + /** + * An instance of [DiktatProcessorListener.Empty] + */ + val empty = Empty() + + /** + * @param listeners + * @return a single [DiktatProcessorListener] which uses all provided [listeners] + */ + operator fun invoke(vararg listeners: DiktatProcessorListener): DiktatProcessorListener = object : DiktatProcessorListener { + override fun beforeAll() = listeners.forEach(DiktatProcessorListener::beforeAll) + override fun before(file: Path) = listeners.forEach { it.before(file) } + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = listeners.forEach { it.onError(file, error, isCorrected) } + override fun after(file: Path) = listeners.forEach { it.after(file) } + override fun afterAll() = listeners.forEach(DiktatProcessorListener::afterAll) + } + + /** + * An implementation of [DiktatProcessorListener] which counts [DiktatError]s + */ + fun AtomicInteger.countErrorsAsProcessorListener(): DiktatProcessorListener = object : DiktatProcessorListener { + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) { + incrementAndGet() + } + } + + /** + * An implementation of [DiktatProcessorListener] which closes [AutoCloseable] at the end + */ + fun AutoCloseable.closeAfterAllAsProcessorListener(): DiktatProcessorListener = object : Empty() { + override fun afterAll() { + this@closeAfterAllAsProcessorListener.close() + } + } + } +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt new file mode 100644 index 0000000000..55f054f3ed --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt @@ -0,0 +1,52 @@ +package org.cqfn.diktat.api + +import java.io.OutputStream +import java.nio.file.Path + +typealias DiktatReporter = DiktatProcessorListener + +/** + * A factory to create [DiktatReporter] + */ +interface DiktatReporterFactory : Function3 { + /** + * Set of supported IDs + */ + val ids: Set + + /** + * ID of [DiktatReporter] for plain output + */ + val plainId: String + + /** + * Names of color for plain output + */ + val colorNamesInPlain: Set + + /** + * @param id ID of [DiktatReporter] + * @param outputStream + * @param sourceRootDir a dir to detect relative path for processing files + * @return created [DiktatReporter] + */ + override operator fun invoke( + id: String, + outputStream: OutputStream, + sourceRootDir: Path, + ): DiktatReporter + + /** + * @param outputStream + * @param sourceRootDir a dir to detect relative path for processing files + * @param colorName name of color for colorful output, `null` means to disable colorization. + * @param groupByFile + * @return [DiktatReporter] for plain output + */ + fun createPlain( + outputStream: OutputStream, + sourceRootDir: Path, + colorName: String? = null, + groupByFile: Boolean = false, + ): DiktatReporter +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt new file mode 100644 index 0000000000..c63e0dfba7 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRule.kt @@ -0,0 +1,26 @@ +package org.cqfn.diktat.api + +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** +* This is a base interface for diktat's rules +*/ +interface DiktatRule : Function3 { + /** + * A unique ID of this rule. + */ + val id: String + + /** + * This method is going to be executed for each node in AST (in DFS fashion). + * + * @param node AST node + * @param autoCorrect indicates whether rule should attempt autocorrection + * @param emitter a way for rule to notify about a violation (lint error) + */ + override fun invoke( + node: ASTNode, + autoCorrect: Boolean, + emitter: DiktatErrorEmitter, + ) +} diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt new file mode 100644 index 0000000000..18700f5a24 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSet.kt @@ -0,0 +1,10 @@ +package org.cqfn.diktat.api + +/** + * A group of [DiktatRule]'s as a single set. + * + * @property rules diktat rules. + */ +data class DiktatRuleSet( + val rules: List +) diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt new file mode 100644 index 0000000000..5f66e87e64 --- /dev/null +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleSetFactory.kt @@ -0,0 +1,26 @@ +package org.cqfn.diktat.api + +import java.nio.file.Path +import kotlin.io.path.absolutePathString + +/** + * A factory which creates a [DiktatRuleSet]. + */ +interface DiktatRuleSetFactory : Function0 { + /** + * @return the default instance of [DiktatRuleSet] + */ + override operator fun invoke(): DiktatRuleSet + + /** + * @param configFile a path to file with configuration for diktat (`diktat-analysis.yml`) + * @return created [DiktatRuleSet] using [configFile] + */ + fun create(configFile: String): DiktatRuleSet + + /** + * @param configFile a file with configuration for diktat (`diktat-analysis.yml`) + * @return created [DiktatRuleSet] using [configFile] + */ + fun create(configFile: Path): DiktatRuleSet = create(configFile.absolutePathString()) +} diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts new file mode 100644 index 0000000000..8ab8256704 --- /dev/null +++ b/diktat-cli/build.gradle.kts @@ -0,0 +1,23 @@ +@Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 +plugins { + id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") + id("org.cqfn.diktat.buildutils.code-quality-convention") + id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") + alias(libs.plugins.kotlin.plugin.serialization) +} + +project.description = "This module builds diktat-runner implementation using ktlint as CLI" + +dependencies { + api(projects.diktatApi) + implementation(projects.diktatRules) + implementation(projects.diktatKtlintEngine) + implementation(libs.kotlinx.cli) + implementation(libs.log4j2.core) + implementation(libs.ktlint.reporter.baseline) + implementation(libs.ktlint.reporter.checkstyle) + implementation(libs.ktlint.reporter.html) + implementation(libs.ktlint.reporter.json) + implementation(libs.ktlint.reporter.plain) + implementation(libs.ktlint.reporter.sarif) +} diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt new file mode 100644 index 0000000000..780c3ff04f --- /dev/null +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -0,0 +1,76 @@ +/** + * The file contains main method + */ + +package org.cqfn.diktat + +import org.cqfn.diktat.cli.DiktatMode +import org.cqfn.diktat.api.DiktatProcessorListener +import org.cqfn.diktat.cli.DiktatProperties +import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl +import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl +import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript +import org.cqfn.diktat.util.tryToPathIfExists +import org.cqfn.diktat.util.walkByGlob +import mu.KotlinLogging +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.readText +import kotlin.io.path.writeText + +private val log = KotlinLogging.logger { } + +@Suppress( + "LongMethod", + "TOO_LONG_FUNCTION" +) +fun main(args: Array) { + val diktatReporterFactory = DiktatReporterFactoryImpl() + val properties = DiktatProperties.parse(diktatReporterFactory, args) + properties.configureLogger() + + log.debug { + "Loading diktatRuleSet using config ${properties.config}" + } + val diktatRuleSet = DiktatRuleSetFactoryImpl().create(properties.config) + val diktatProcessor = DiktatProcessorFactoryImpl().invoke(diktatRuleSet) + val currentFolder = Paths.get(".") + val reporter = properties.reporter(diktatReporterFactory, currentFolder) + + log.debug { + "Resolving files by patterns: ${properties.patterns}" + } + val files = properties.patterns + .asSequence() + .flatMap { pattern -> + pattern.tryToPathIfExists()?.let { sequenceOf(it) } + ?: currentFolder.walkByGlob(pattern) + } + .filter { file -> file.isKotlinCodeOrScript() } + .distinct() + .map { it.normalize() } + + val loggingListener = object : DiktatProcessorListener.Companion.Empty() { + override fun before(file: Path) { + log.debug { + "Start processing the file: $file" + } + } + } + when (properties.mode) { + DiktatMode.CHECK -> diktatProcessor.checkAll( + listener = DiktatProcessorListener(loggingListener, reporter), + files = files, + ) + DiktatMode.FIX -> diktatProcessor.fixAll( + listener = DiktatProcessorListener(loggingListener, reporter), + files = files, + ) { file, formatterText -> + if (file.readText(StandardCharsets.UTF_8) != formatterText) { + file.writeText(formatterText, Charsets.UTF_8) + } + } + } +} diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt new file mode 100644 index 0000000000..1618e008f4 --- /dev/null +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatMode.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat.cli + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Mode of `diktat` + */ +@Serializable +enum class DiktatMode { + @SerialName("check") + CHECK, + @SerialName("fix") + FIX, + ; +} diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt new file mode 100644 index 0000000000..593880e368 --- /dev/null +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -0,0 +1,225 @@ +package org.cqfn.diktat.cli + +import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.api.DiktatReporterFactory +import org.cqfn.diktat.common.config.rules.DIKTAT +import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF +import com.pinterest.ktlint.core.Reporter +import com.pinterest.ktlint.core.ReporterProvider +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.LoggerContext +import org.slf4j.event.Level +import java.io.PrintStream +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.createDirectories +import kotlin.io.path.outputStream +import kotlin.system.exitProcess +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.default +import kotlinx.cli.vararg + +/** + * @property config path to `diktat-analysis.yml` + * @property mode mode of `diktat` + * @property reporterProviderId + * @property output + * @property patterns + */ +data class DiktatProperties( + val config: String, + val mode: DiktatMode, + val reporterProviderId: String, + val output: String?, + private val groupByFileInPlain: Boolean, + private val colorNameInPlain: String?, + private val logLevel: Level, + val patterns: List, +) { + /** + * @param diktatReporterFactory + * @param sourceRootDir + * @return a configured [Reporter] + */ + fun reporter( + diktatReporterFactory: DiktatReporterFactory, + sourceRootDir: Path, + ): DiktatReporter { + val outputStream = output + ?.let { Paths.get(it) } + ?.also { it.parent.createDirectories() } + ?.outputStream() + ?.let { PrintStream(it) } + ?: System.out + return if (reporterProviderId == diktatReporterFactory.plainId) { + diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) + } else { + require(colorNameInPlain == null) { + "colorization is applicable only for plain reporter" + } + require(!groupByFileInPlain) { + "groupByFile is applicable only for plain reporter" + } + diktatReporterFactory.invoke(reporterProviderId, outputStream, sourceRootDir) + } + } + + /** + * Configure logger level using [logLevel] + */ + fun configureLogger() { + // set log level + LogManager.getContext(false) + .let { it as LoggerContext } + .also { ctx -> + ctx.configuration.rootLogger.level = when (logLevel) { + Level.ERROR -> org.apache.logging.log4j.Level.ERROR + Level.WARN -> org.apache.logging.log4j.Level.WARN + Level.INFO -> org.apache.logging.log4j.Level.INFO + Level.DEBUG -> org.apache.logging.log4j.Level.DEBUG + Level.TRACE -> org.apache.logging.log4j.Level.TRACE + } + } + .updateLoggers() + } + + companion object { + /** + * @param diktatReporterFactory + * @param args cli arguments + * @return parsed [DiktatProperties] + */ + @Suppress( + "LongMethod", + "TOO_LONG_FUNCTION" + ) + fun parse( + diktatReporterFactory: DiktatReporterFactory, + args: Array, + ): DiktatProperties { + val parser = ArgParser(DIKTAT) + val config: String by parser.option( + type = ArgType.String, + fullName = "config", + shortName = "c", + description = "Specify the location of the YAML configuration file. By default, $DIKTAT_ANALYSIS_CONF in the current directory is used.", + ).default(DIKTAT_ANALYSIS_CONF) + val mode: DiktatMode by parser.option( + type = ArgType.Choice(), + fullName = "mode", + shortName = "m", + description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." + ).default(DiktatMode.CHECK) + val reporterProviderId: String by parser.reporterProviderId(diktatReporterFactory) + val output: String? by parser.option( + type = ArgType.String, + fullName = "output", + shortName = "o", + description = "Redirect the reporter output to a file.", + ) + val groupByFileInPlain: Boolean by parser.option( + type = ArgType.Boolean, + fullName = "plain-group-by-file", + shortName = null, + description = "A flag for plain reporter" + ).default(false) + val colorName: String? by parser.colorName(diktatReporterFactory) + val logLevel: Level by parser.option( + type = ArgType.Choice(), + fullName = "log-level", + shortName = "l", + description = "Enable the output with specific level", + ).default(Level.INFO) + val patterns: List by parser.argument( + type = ArgType.String, + description = "A list of files to process by diktat" + ).vararg() + + parser.addOptionAndShowResourceWithExit( + fullName = "version", + shortName = "V", + description = "Output version information and exit.", + args = args, + resourceName = "META-INF/diktat/version" + ) + parser.addOptionAndShowResourceWithExit( + fullName = "license", + shortName = null, + description = "Display the license and exit.", + args = args, + resourceName = "META-INF/diktat/LICENSE", + ) + + parser.parse(args) + return DiktatProperties( + config = config, + mode = mode, + reporterProviderId = reporterProviderId, + output = output, + groupByFileInPlain = groupByFileInPlain, + colorNameInPlain = colorName, + logLevel = logLevel, + patterns = patterns, + ) + } + + /** + * @param diktatReporterFactory + * @return a single [ReporterProvider] as parsed cli arg + */ + private fun ArgParser.reporterProviderId(diktatReporterFactory: DiktatReporterFactory) = option( + type = ArgType.Choice( + choices = diktatReporterFactory.ids.toList(), + toVariant = { it }, + variantToString = { it }, + ), + fullName = "reporter", + shortName = "r", + description = "The reporter to use" + ) + .default(diktatReporterFactory.plainId) + + /** + * @param diktatReporterFactory + * @return a single and optional color name as parsed cli args + */ + private fun ArgParser.colorName(diktatReporterFactory: DiktatReporterFactory) = this.option( + type = ArgType.Choice( + choices = diktatReporterFactory.colorNamesInPlain.toList(), + toVariant = { it }, + variantToString = { it }, + ), + fullName = "plain-color", + shortName = null, + description = "Colorize the output.", + ) + + private fun ArgParser.addOptionAndShowResourceWithExit( + fullName: String, + shortName: String?, + description: String, + args: Array, + resourceName: String, + ) { + // add here to print in help + option( + type = ArgType.Boolean, + fullName = fullName, + shortName = shortName, + description = description + ) + if (args.contains("--$fullName") || shortName?.let { args.contains("-$it") } == true) { + @Suppress("DEBUG_PRINT", "ForbiddenMethodCall") + print(readFromResource(resourceName)) + exitProcess(0) + } + } + + private fun readFromResource(resourceName: String): String = DiktatProperties::class.java + .classLoader + .getResource(resourceName) + ?.readText() + ?: error("Resource $resourceName not found") + } +} diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt new file mode 100644 index 0000000000..22f5027272 --- /dev/null +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt @@ -0,0 +1,36 @@ +/** + * This class contains util methods to operate with java.nio.file.Path + */ + +package org.cqfn.diktat.util + +import java.nio.file.InvalidPathException +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.PathWalkOption +import kotlin.io.path.exists +import kotlin.io.path.walk + +/** + * Create a matcher and return a filter that uses it. + * + * @param glob glob pattern to filter files + * @return a sequence of files which matches to [glob] + */ +@OptIn(ExperimentalPathApi::class) +fun Path.walkByGlob(glob: String): Sequence = + fileSystem.getPathMatcher("glob:$glob") + .let { matcher -> + this.walk(PathWalkOption.INCLUDE_DIRECTORIES) + .filter { matcher.matches(it) } + } + +/** + * @return path or null if path is invalid or doesn't exist + */ +fun String.tryToPathIfExists(): Path? = try { + Paths.get(this).takeIf { it.exists() } +} catch (e: InvalidPathException) { + null +} diff --git a/diktat-ktlint-engine/build.gradle.kts b/diktat-ktlint-engine/build.gradle.kts new file mode 100644 index 0000000000..1a0441e6ac --- /dev/null +++ b/diktat-ktlint-engine/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") + id("org.cqfn.diktat.buildutils.code-quality-convention") + id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") +} + +project.description = "This module builds diktat-runner implementation using ktlint" + +dependencies { + api(projects.diktatApi) + implementation(projects.diktatCommon) + implementation(libs.ktlint.core) + implementation(libs.ktlint.reporter.baseline) + implementation(libs.ktlint.reporter.checkstyle) + implementation(libs.ktlint.reporter.html) + implementation(libs.ktlint.reporter.json) + implementation(libs.ktlint.reporter.plain) + implementation(libs.ktlint.reporter.sarif) + + testImplementation(libs.junit.jupiter) + testImplementation(libs.junit.platform.suite) + testImplementation(libs.assertj.core) + testImplementation(libs.mockito) +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt new file mode 100644 index 0000000000..71391e8923 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt @@ -0,0 +1,27 @@ +/** + * Utilities related to logging + */ + +package org.cqfn.diktat.common.utils + +import com.pinterest.ktlint.core.initKtLintKLogger +import mu.KotlinLogging +import kotlin.reflect.KClass + +/** + * Create a logger using [KotlinLogging] and configure it by ktlint's mechanism + * + * @param func empty fun which is used to get enclosing class name + * @return a logger + */ +fun KotlinLogging.loggerWithKtlintConfig(func: () -> Unit) = + logger(func).initKtLintKLogger() + +/** + * Create a logger using [KotlinLogging] and configure it by ktlint's mechanism + * + * @param clazz a class for which logger is needed + * @return a logger + */ +fun KotlinLogging.loggerWithKtlintConfig(clazz: KClass<*>) = + logger(clazz.java.name).initKtLintKLogger() diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt new file mode 100644 index 0000000000..ae04bbb8c9 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt @@ -0,0 +1,42 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatBaseline +import org.cqfn.diktat.api.DiktatBaselineFactory +import org.cqfn.diktat.api.DiktatProcessorListener +import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap +import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap +import com.pinterest.ktlint.core.internal.loadBaseline +import com.pinterest.ktlint.reporter.baseline.BaselineReporter +import java.io.PrintStream +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.outputStream + +/** + * A factory to create or generate [DiktatBaseline] using `KtLint` + */ +@Suppress("DEPRECATION") +class DiktatBaselineFactoryImpl : DiktatBaselineFactory { + override fun tryToLoad(baselineFile: Path, sourceRootDir: Path): DiktatBaseline? { + return loadBaseline(baselineFile.absolutePathString()) + .takeUnless { it.baselineGenerationNeeded } + ?.let { ktLintBaseline -> + DiktatBaseline { file -> + ktLintBaseline.baselineRules + ?.get(file.relativePathStringTo(sourceRootDir)) + .orEmpty() + .map { it.wrap() } + .toSet() + } + } + } + + override fun generator(baselineFile: Path, sourceRootDir: Path): DiktatProcessorListener { + val outputStream = baselineFile.outputStream() + return DiktatProcessorListener( + BaselineReporter(PrintStream(outputStream)).wrap(sourceRootDir), + outputStream.closeAfterAllAsProcessorListener() + ) + } +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt new file mode 100644 index 0000000000..4b309ad50f --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatErrorImpl.kt @@ -0,0 +1,37 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatError +import com.pinterest.ktlint.core.LintError + +/** + * Wrapper for KtLint error + * + * @property lintError + */ +data class DiktatErrorImpl( + private val lintError: LintError +) : DiktatError { + override fun getLine(): Int = lintError.line + + override fun getCol(): Int = lintError.col + + override fun getRuleId(): String = lintError.ruleId + + override fun getDetail(): String = lintError.detail + + override fun canBeAutoCorrected(): Boolean = lintError.canBeAutoCorrected + + companion object { + /** + * @return [DiktatError] from KtLint [LintError] + */ + fun LintError.wrap(): DiktatError = DiktatErrorImpl(this) + + /** + * @return KtLint [LintError] from [DiktatError] or exception + */ + fun DiktatError.unwrap(): LintError = (this as? DiktatErrorImpl)?.lintError + ?: error("Unsupported wrapper of ${DiktatError::class.java.simpleName}: ${this::class.java.canonicalName}") + } +} + diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt new file mode 100644 index 0000000000..fca4f39938 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt @@ -0,0 +1,41 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.DiktatProcessor +import org.cqfn.diktat.DiktatProcessorFactory +import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.api.EditorConfigOverride +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.readText + +/** + * A factory to create [DiktatProcessor] using [DiktatProcessorFactory] and `KtLint` as engine + */ +class DiktatProcessorFactoryImpl : DiktatProcessorFactory { + override fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor = object : DiktatProcessor() { + override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(ktLintParams(diktatRuleSet, file, callback.unwrap())) + override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(ktLintParams(diktatRuleSet, file, callback.unwrap())) + } + + @Suppress("DEPRECATION") + private fun ktLintParams( + diktatRuleSet: DiktatRuleSet, + file: Path, + callback: LintErrorCallback, + ): KtLint.ExperimentalParams = KtLint.ExperimentalParams( + fileName = file.absolutePathString(), + text = file.readText(StandardCharsets.UTF_8), + ruleSets = setOf(diktatRuleSet.toKtLint()), + userData = emptyMap(), + cb = callback, + script = false, // internal API of KtLint + editorConfigPath = null, + debug = false, // we do not use it + editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride, + isInvokedFromCli = false + ) +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt new file mode 100644 index 0000000000..8fbb804a43 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -0,0 +1,81 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.api.DiktatReporterFactory +import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap +import com.pinterest.ktlint.core.ReporterProvider +import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider +import com.pinterest.ktlint.reporter.html.HtmlReporterProvider +import com.pinterest.ktlint.reporter.json.JsonReporterProvider +import com.pinterest.ktlint.reporter.plain.PlainReporterProvider +import com.pinterest.ktlint.reporter.plain.internal.Color +import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider +import java.io.OutputStream +import java.io.PrintStream +import java.nio.file.Path +import kotlin.io.path.pathString + +/** + * A factory to create [DiktatReporter] using `KtLint` + */ +class DiktatReporterFactoryImpl : DiktatReporterFactory { + private val plainReporterProvider = PlainReporterProvider() + + /** + * All [ReporterProvider] which __KtLint__ provides + */ + private val reporterProviders = setOf( + plainReporterProvider, + JsonReporterProvider(), + SarifReporterProvider(), + CheckStyleReporterProvider(), + HtmlReporterProvider(), + ) + .associateBy { it.id } + + override val ids: Set + get() = reporterProviders.keys + + override val plainId: String + get() = plainReporterProvider.id + + override val colorNamesInPlain: Set + get() = Color.values().map { it.name }.toSet() + + override fun invoke( + id: String, + outputStream: OutputStream, + sourceRootDir: Path, + ): DiktatReporter { + val reporterProvider = reporterProviders[id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") + if (reporterProvider is SarifReporterProvider) { + System.setProperty("user.home", sourceRootDir.pathString) + } + val opt = if (reporterProvider is PlainReporterProvider) { + mapOf("color" to "DARK_GRAY") + } else { + emptyMap() + } + return reporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) + } + + override fun createPlain(outputStream: OutputStream, sourceRootDir: Path, colorName: String?, groupByFile: Boolean): DiktatReporter { + val opt = buildMap { + colorName?.let { + put("color", true) + put("color_name", it) + } ?: run { + put("color", false) + put("color_name", Color.DARK_GRAY) + } + if (groupByFile) { + put("group_by_file", true) + } + }.mapValues { it.value.toString() } + return plainReporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) + } + + companion object { + private fun OutputStream.asPrintStream(): PrintStream = (this as? PrintStream) ?: PrintStream(this) + } +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt new file mode 100644 index 0000000000..a6e1b437d0 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterImpl.kt @@ -0,0 +1,35 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatError +import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap +import com.pinterest.ktlint.core.Reporter +import java.nio.file.Path + +/** + * [DiktatReporter] using __KtLint__ + */ +class DiktatReporterImpl( + private val ktLintReporter: Reporter, + private val sourceRootDir: Path, +) : DiktatReporter { + override fun beforeAll(): Unit = ktLintReporter.beforeAll() + override fun before(file: Path): Unit = ktLintReporter.before(file.relativePathStringTo(sourceRootDir)) + override fun onError(file: Path, error: DiktatError, isCorrected: Boolean) = ktLintReporter.onLintError(file.relativePathStringTo(sourceRootDir), error.unwrap(), isCorrected) + override fun after(file: Path): Unit = ktLintReporter.after(file.relativePathStringTo(sourceRootDir)) + override fun afterAll(): Unit = ktLintReporter.beforeAll() + + companion object { + /** + * @param sourceRootDir + * @return [DiktatReporter] which wraps __KtLint__'s [Reporter] + */ + fun Reporter.wrap(sourceRootDir: Path): DiktatReporter = DiktatReporterImpl(this, sourceRootDir) + + /** + * @return __KtLint__'s [Reporter] + */ + internal fun DiktatReporter.unwrap(): Reporter = (this as? DiktatReporterImpl)?.ktLintReporter + ?: error("Unsupported wrapper of ${DiktatReporter::class.java.simpleName}: ${this::class.java.canonicalName}") + } +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt new file mode 100644 index 0000000000..46bf92c8f9 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt @@ -0,0 +1,35 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatRule +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.RuleSet + +/** + * This is a wrapper around __KtLint__'s [RuleSet] which adjusts visitorModifiers for all rules to keep order with prevRule + * Added as a workaround after introducing a new logic for sorting KtLint Rules: https://github.com/pinterest/ktlint/issues/1478 + * + * @param diktatRuleSet the rules which belong to the current [DiktatRuleSet]. + */ +class KtLintRuleSetWrapper private constructor( + diktatRuleSet: DiktatRuleSet, +) : RuleSet(DIKTAT_RULE_SET_ID, rules = wrapRules(diktatRuleSet.rules)) { + companion object { + /** + * @return __KtLint__'s [RuleSet] created from [DiktatRuleSet] + */ + fun DiktatRuleSet.toKtLint(): RuleSet = KtLintRuleSetWrapper(this) + + private fun wrapRules(rules: List): Array { + if (rules.isEmpty()) { + return emptyArray() + } + return rules.runningFold(null as KtLintRuleWrapper?) { prevRule, diktatRule -> + KtLintRuleWrapper(diktatRule, prevRule) + } + .filterNotNull() + .toTypedArray() + } + } +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt new file mode 100644 index 0000000000..19d3e7716a --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt @@ -0,0 +1,56 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatRule +import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId +import com.pinterest.ktlint.core.Rule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +private typealias EmitType = (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + +/** + * This is a wrapper around __KtLint__'s [Rule] which adjusts visitorModifiers to keep order with prevRule. + * @property rule + */ +class KtLintRuleWrapper( + val rule: DiktatRule, + prevRule: KtLintRuleWrapper? = null, +) : Rule( + id = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID), + visitorModifiers = createVisitorModifiers(rule, prevRule), +) { + @Deprecated( + "Marked for deletion in ktlint 0.48.0", + replaceWith = ReplaceWith("beforeVisitChildNodes(node, autoCorrect, emit)"), + ) + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: EmitType, + ) = rule.invoke(node, autoCorrect, emit) + + companion object { + private fun createVisitorModifiers( + rule: DiktatRule, + prevRule: KtLintRuleWrapper?, + ): Set = prevRule?.id?.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID) + ?.let { previousRuleId -> + val ruleId = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID) + require(ruleId != previousRuleId) { + "PrevRule has same ID as rule: $ruleId" + } + setOf( + VisitorModifier.RunAfterRule( + ruleId = previousRuleId, + loadOnlyWhenOtherRuleIsLoaded = false, + runOnlyWhenOtherRuleIsEnabled = false + ) + ) + } ?: emptySet() + + /** + * @return a rule to which a logic is delegated + */ + internal fun Rule.unwrap(): DiktatRule = (this as? KtLintRuleWrapper)?.rule ?: error("Provided rule ${javaClass.simpleName} is not wrapped by diktat") + } +} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt new file mode 100644 index 0000000000..cff1ce20b3 --- /dev/null +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintUtils.kt @@ -0,0 +1,96 @@ +/** + * This file contains util methods for __KtLint__ + */ + +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatCallback +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap +import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap +import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.KtLint.ExperimentalParams +import com.pinterest.ktlint.core.LintError +import mu.KotlinLogging +import org.intellij.lang.annotations.Language +import java.nio.file.Path +import kotlin.io.path.invariantSeparatorsPathString +import kotlin.io.path.relativeTo + +typealias LintErrorCallback = (LintError, Boolean) -> Unit + +private val log = KotlinLogging.logger { } + +val defaultCallback = DiktatCallback { error, _ -> + log.warn { "Received linting error: $error" } +} + +/** + * @param sourceRootDir + * @return relative path to [sourceRootDir] as [String] + */ +fun Path.relativePathStringTo(sourceRootDir: Path): String = relativeTo(sourceRootDir).invariantSeparatorsPathString + +/** + * @return [DiktatCallback] from KtLint [LintErrorCallback] + */ +fun LintErrorCallback.wrap(): DiktatCallback = DiktatCallback { error, isCorrected -> + this(error.unwrap(), isCorrected) +} + +/** + * @return KtLint [LintErrorCallback] from [DiktatCallback] or exception + */ +fun DiktatCallback.unwrap(): LintErrorCallback = { error, isCorrected -> + this(error.wrap(), isCorrected) +} + +/** + * Enables ignoring autocorrected errors when in "fix" mode (i.e. when + * [KtLint.format] is invoked). + * + * Before version 0.47, _Ktlint_ only reported non-corrected errors in "fix" + * mode. + * Now, this has changed. + * + * @receiver the instance of _Ktlint_ parameters. + * @return the instance with the [callback][ExperimentalParams.cb] modified in + * such a way that it ignores corrected errors. + * @see KtLint.format + * @see ExperimentalParams.cb + * @since 1.2.4 + */ +private fun ExperimentalParams.ignoreCorrectedErrors(): ExperimentalParams = + copy(cb = { error: LintError, corrected: Boolean -> + if (!corrected) { + cb(error, false) + } + }) + +/** + * @param ruleSetSupplier + * @param text + * @param fileName + * @param cb callback to be called on unhandled [LintError]s + * @return formatted code + */ +@Suppress("LAMBDA_IS_NOT_LAST_PARAMETER") +fun format( + ruleSetSupplier: () -> DiktatRuleSet, + @Language("kotlin") text: String, + fileName: String, + cb: DiktatCallback = defaultCallback +): String { + val ruleSets = listOf(ruleSetSupplier().toKtLint()) + return KtLint.format( + ExperimentalParams( + text = text, + ruleSets = ruleSets, + fileName = fileName.removeSuffix("_copy"), + script = fileName.removeSuffix("_copy").endsWith("kts"), + cb = cb.unwrap(), + debug = true, + ).ignoreCorrectedErrors() + ) +} diff --git a/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt b/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt new file mode 100644 index 0000000000..3d5014391e --- /dev/null +++ b/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt @@ -0,0 +1,161 @@ +package org.cqfn.diktat.ktlint + +import org.cqfn.diktat.api.DiktatErrorEmitter +import org.cqfn.diktat.api.DiktatRule +import org.cqfn.diktat.api.DiktatRuleSet +import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId +import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.unwrap +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.Rule +import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class KtLintRuleSetWrapperTest { + @Test + fun `check KtLintRuleSetWrapper with duplicate`() { + val rule = mockRule("rule") + Assertions.assertThrows(IllegalArgumentException::class.java) { + DiktatRuleSet(listOf(rule, rule)).toKtLint() + } + } + + @Test + fun `check OrderedRule`() { + val ruleSetId = "id" + + val rule1 = mockRule(id = "rule-first".qualifiedWithRuleSetId(ruleSetId)) + val rule2 = mockRule(id = "rule-second".qualifiedWithRuleSetId(ruleSetId)) + + val orderedRuleSet = DiktatRuleSet(listOf(rule1, rule2)).toKtLint() + + val orderedRuleSetIterator = orderedRuleSet.rules.iterator() + val orderedRule1 = orderedRuleSetIterator.next() + val orderedRule2 = orderedRuleSetIterator.next() + Assertions.assertFalse(orderedRuleSetIterator.hasNext(), "Extra elements after ordering") + + Assertions.assertEquals(rule1, orderedRule1.unwrap(), "First rule is modified") + + orderedRule2.visitorModifiers + .filterIsInstance() + .also { + Assertions.assertEquals(1, it.size, + "Found invalid count of Rule.VisitorModifier.RunAfterRule") + } + .first() + .let { + Assertions.assertEquals(rule1.id.qualifiedWithRuleSetId(ruleSetId), it.ruleId, + "Invalid ruleId in Rule.VisitorModifier.RunAfterRule") + } + } + + @Test + @Suppress("TOO_LONG_FUNCTION") + fun `KtLint keeps order with RuleVisitorModifierRunAfterRule`() { + val actualRuleInvocationOrder: MutableList = mutableListOf() + val onVisit: (DiktatRule) -> Unit = { rule -> + actualRuleInvocationOrder += rule.id + } + val ruleSetId = "id" + val rules: List = sequenceOf("ccc", "bbb", "aaa").map { ruleId -> + mockRule( + id = ruleId.qualifiedWithRuleSetId(ruleSetId), + onVisit = onVisit + ) + }.toList() + assertThat(rules).isNotEmpty + + /* + * Make sure the rules are not sorted by id. + */ + val rulesOrderedById: List = rules.sortedBy(DiktatRule::id) + assertThat(rules).containsExactlyInAnyOrder(*rulesOrderedById.toTypedArray()) + assertThat(rules).isNotEqualTo(rulesOrderedById) + + /* + * Make sure OrderedRuleSet preserves the order. + */ + val ruleSet = DiktatRuleSet(rules).toKtLint() + assertThat(ruleSet.rules.map(Rule::id)).containsExactlyElementsOf(rules.map(DiktatRule::id)) + + @Language("kotlin") + val code = "fun foo() { }" + + KtLint.lint( + KtLint.ExperimentalParams( + fileName = "TestFileName.kt", + text = code, + ruleSets = listOf(ruleSet), + cb = { _, _ -> }, + ) + ) + + val ruleCount = rules.size + assertThat(actualRuleInvocationOrder) + .describedAs("The ordered list of rule invocations") + .matches({ order -> + order.size % ruleCount == 0 + }, "has a size which is multiple of $ruleCount") + + /* + * This is the count of AST nodes in `code` above. + */ + val astNodeCount = actualRuleInvocationOrder.size / ruleCount + + /*- + * This is new in ktlint 0.47. + * Previously, rules were applied in this sequence: + * + * A -> B -> C (File) + * | + * V + * A -> B -> C (Node) + * | + * V + * A -> B -> C (Leaf) + * + * Now, each rule is recursively applied to all AST nodes, and then the + * control is passed to the next rule: + * + * A(File) -> A(Node) -> A(Leaf) + * | + * V + * B(File) -> B(Node) -> B(Leaf) + * | + * V + * C(File) -> C(Node) -> C(Leaf) + * + * val expectedRuleInvocationOrder = rules.asSequence() + * .map(Rule::id) + * .flatMap { ruleId -> + * generateSequence { ruleId }.take(astNodeCount) + * } + * .toList() + */ + val expectedRuleInvocationOrder = generateSequence { + rules.map(DiktatRule::id) + } + .take(astNodeCount) + .flatten() + .toList() + + assertThat(actualRuleInvocationOrder) + .containsExactlyElementsOf(expectedRuleInvocationOrder) + } + + companion object { + private fun mockRule( + id: String, + onVisit: (DiktatRule) -> Unit = { } + ): DiktatRule = object : DiktatRule { + override val id: String + get() = id + override fun invoke(node: ASTNode, autoCorrect: Boolean, emitter: DiktatErrorEmitter) { + onVisit(this) + } + } + } +} From 0ca2da5f400023252cee8300d60185e4cc876bd6 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 11:52:20 +0300 Subject: [PATCH 12/34] reverted unwanted changes --- build.gradle.kts | 2 -- diktat-cli/build.gradle.kts | 11 ++------ .../config/reader/JsonResourceConfigReader.kt | 2 +- .../common/config/rules/RulesConfigReader.kt | 2 +- .../cqfn/diktat/common/utils/LoggingUtils.kt | 27 ------------------- 5 files changed, 4 insertions(+), 40 deletions(-) delete mode 100644 diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt diff --git a/build.gradle.kts b/build.gradle.kts index 92859aaf31..11af66166a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,8 +32,6 @@ tasks.create("generateLibsForDiktatSnapshot") { val dir = rootProject.buildDir.resolve("diktat-snapshot") val dependencies = setOf( - rootProject.project(":diktat-api"), - rootProject.project(":diktat-ktlint-engine"), rootProject.project(":diktat-common"), rootProject.project(":diktat-api"), rootProject.project(":diktat-ktlint-engine"), diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 8ab8256704..925e255a3e 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -6,18 +6,11 @@ plugins { alias(libs.plugins.kotlin.plugin.serialization) } -project.description = "This module builds diktat-runner implementation using ktlint as CLI" +project.description = "This module builds diktat-cli to run diktat as CLI using ktlint" dependencies { - api(projects.diktatApi) - implementation(projects.diktatRules) implementation(projects.diktatKtlintEngine) + implementation(projects.diktatRules) implementation(libs.kotlinx.cli) implementation(libs.log4j2.core) - implementation(libs.ktlint.reporter.baseline) - implementation(libs.ktlint.reporter.checkstyle) - implementation(libs.ktlint.reporter.html) - implementation(libs.ktlint.reporter.json) - implementation(libs.ktlint.reporter.plain) - implementation(libs.ktlint.reporter.sarif) } 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 aea34ca0e3..6da9a9a3d3 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 @@ -56,6 +56,6 @@ abstract class JsonResourceConfigReader { protected abstract fun parseResource(fileStream: BufferedReader): T companion object { - private val log = KotlinLogging.logger { } + private val log = KotlinLogging.logger {} } } 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 00e027be27..29c8ef8e02 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 @@ -105,7 +105,7 @@ open class RulesConfigReader(override val classLoader: ClassLoader) : JsonResour } companion object { - internal val log: KLogger = KotlinLogging.logger { } + internal val log: KLogger = KotlinLogging.logger {} } } diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt deleted file mode 100644 index 71391e8923..0000000000 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/common/utils/LoggingUtils.kt +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Utilities related to logging - */ - -package org.cqfn.diktat.common.utils - -import com.pinterest.ktlint.core.initKtLintKLogger -import mu.KotlinLogging -import kotlin.reflect.KClass - -/** - * Create a logger using [KotlinLogging] and configure it by ktlint's mechanism - * - * @param func empty fun which is used to get enclosing class name - * @return a logger - */ -fun KotlinLogging.loggerWithKtlintConfig(func: () -> Unit) = - logger(func).initKtLintKLogger() - -/** - * Create a logger using [KotlinLogging] and configure it by ktlint's mechanism - * - * @param clazz a class for which logger is needed - * @return a logger - */ -fun KotlinLogging.loggerWithKtlintConfig(clazz: KClass<*>) = - logger(clazz.java.name).initKtLintKLogger() From d87a462c142da72bb3e9875f6ebbcc387b6e9d12 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 12:19:06 +0300 Subject: [PATCH 13/34] fixed DiktatMain to use a new DiktatRunner --- .../org/cqfn/diktat/DiktatRunnerArguments.kt | 12 +++- .../org/cqfn/diktat/DiktatRunnerFactory.kt | 24 ++++++- .../cqfn/diktat/api/DiktatReporterFactory.kt | 2 +- .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 71 +++++++------------ .../org/cqfn/diktat/cli/DiktatProperties.kt | 68 ++++++++++-------- .../ktlint/DiktatReporterFactoryImpl.kt | 6 +- .../framework/common/LocalCommandExecutor.kt | 2 +- 7 files changed, 98 insertions(+), 87 deletions(-) diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerArguments.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerArguments.kt index 781b77192e..026597824e 100644 --- a/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerArguments.kt +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerArguments.kt @@ -14,7 +14,9 @@ import kotlin.io.path.absolutePathString * @property baselineFile an optional path to file with baseline * @property reporterType type of reporter to report the detected errors * @property reporterOutput output for reporter - * @property loggingListener listener to log diktat runner phases + * @property groupByFileInPlain a flag `groupByFile` which is applicable for plain reporter only, **null** by default + * @property colorNameInPlain a color name which is applicable for plain reporter only, **null** by default + * @property loggingListener listener to log diktat runner phases, [DiktatProcessorListener.empty] by default */ data class DiktatRunnerArguments( val configFileName: String, @@ -23,6 +25,8 @@ data class DiktatRunnerArguments( val baselineFile: Path?, val reporterType: String, val reporterOutput: OutputStream?, + val groupByFileInPlain: Boolean? = null, + val colorNameInPlain: String? = null, val loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty, ) { constructor( @@ -32,7 +36,9 @@ data class DiktatRunnerArguments( baselineFile: Path?, reporterType: String, reporterOutput: OutputStream?, - loggingListener: DiktatProcessorListener, + groupByFileInPlain: Boolean? = null, + colorNameInPlain: String? = null, + loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty, ) : this( configFile.absolutePathString(), sourceRootDir, @@ -40,6 +46,8 @@ data class DiktatRunnerArguments( baselineFile, reporterType, reporterOutput, + groupByFileInPlain, + colorNameInPlain, loggingListener, ) } diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerFactory.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerFactory.kt index dc1fe32221..7e886c787c 100644 --- a/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerFactory.kt +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerFactory.kt @@ -12,12 +12,14 @@ import java.nio.file.Path /** * A factory to create [DiktatRunner] + * + * @property diktatReporterFactory a factory for [DiktatReporter] */ class DiktatRunnerFactory( private val diktatRuleSetFactory: DiktatRuleSetFactory, private val diktatProcessorFactory: DiktatProcessorFactory, private val diktatBaselineFactory: DiktatBaselineFactory, - private val diktatReporterFactory: DiktatReporterFactory, + val diktatReporterFactory: DiktatReporterFactory, ) : Function1 { /** * @param args @@ -27,7 +29,11 @@ class DiktatRunnerFactory( val diktatRuleSet = diktatRuleSetFactory.create(args.configFileName) val processor = diktatProcessorFactory(diktatRuleSet) val (baseline, baselineGenerator) = resolveBaseline(args.baselineFile, args.sourceRootDir) - val (reporter, closer) = resolveReporter(args.reporterType, args.reporterOutput, args.sourceRootDir) + val (reporter, closer) = resolveReporter( + args.reporterType, args.reporterOutput, + args.colorNameInPlain, args.groupByFileInPlain, + args.sourceRootDir + ) return DiktatRunner( diktatProcessor = processor, diktatBaseline = baseline, @@ -53,6 +59,8 @@ class DiktatRunnerFactory( private fun resolveReporter( reporterType: String, reporterOutput: OutputStream?, + colorNameInPlain: String?, + groupByFileInPlain: Boolean?, sourceRootDir: Path, ): Pair { val (outputStream, closeListener) = reporterOutput @@ -60,7 +68,17 @@ class DiktatRunnerFactory( ?: run { System.`out` to DiktatProcessorListener.empty } - val actualReporter = diktatReporterFactory(reporterType, outputStream, sourceRootDir) + val actualReporter = if (reporterType == diktatReporterFactory.plainId) { + diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) + } else { + require(colorNameInPlain == null) { + "colorization is applicable only for plain reporter" + } + require(groupByFileInPlain == null) { + "groupByFile is applicable only for plain reporter" + } + diktatReporterFactory(reporterType, outputStream, sourceRootDir) + } return actualReporter to closeListener } } diff --git a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt index 55f054f3ed..2fe86176bf 100644 --- a/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt +++ b/diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatReporterFactory.kt @@ -47,6 +47,6 @@ interface DiktatReporterFactory : Function3) { - val diktatReporterFactory = DiktatReporterFactoryImpl() - val properties = DiktatProperties.parse(diktatReporterFactory, args) + val diktatRunnerFactory = DiktatRunnerFactory( + DiktatRuleSetFactoryImpl(), + DiktatProcessorFactoryImpl(), + DiktatBaselineFactoryImpl(), + DiktatReporterFactoryImpl(), + ) + val properties = DiktatProperties.parse(diktatRunnerFactory.diktatReporterFactory, args) properties.configureLogger() log.debug { "Loading diktatRuleSet using config ${properties.config}" } - val diktatRuleSet = DiktatRuleSetFactoryImpl().create(properties.config) - val diktatProcessor = DiktatProcessorFactoryImpl().invoke(diktatRuleSet) val currentFolder = Paths.get(".") - val reporter = properties.reporter(diktatReporterFactory, currentFolder) + val diktatRunnerArguments = properties.toRunnerArguments( + sourceRootDir = currentFolder, + loggingListener = loggingListener, + ) - log.debug { - "Resolving files by patterns: ${properties.patterns}" - } - val files = properties.patterns - .asSequence() - .flatMap { pattern -> - pattern.tryToPathIfExists()?.let { sequenceOf(it) } - ?: currentFolder.walkByGlob(pattern) - } - .filter { file -> file.isKotlinCodeOrScript() } - .distinct() - .map { it.normalize() } - - val loggingListener = object : DiktatProcessorListener.Companion.Empty() { - override fun before(file: Path) { - log.debug { - "Start processing the file: $file" - } - } - } + val diktatRunner = diktatRunnerFactory(diktatRunnerArguments) when (properties.mode) { - DiktatMode.CHECK -> diktatProcessor.checkAll( - listener = DiktatProcessorListener(loggingListener, reporter), - files = files, - ) - DiktatMode.FIX -> diktatProcessor.fixAll( - listener = DiktatProcessorListener(loggingListener, reporter), - files = files, - ) { file, formatterText -> - if (file.readText(StandardCharsets.UTF_8) != formatterText) { - file.writeText(formatterText, Charsets.UTF_8) + DiktatMode.CHECK -> diktatRunner.checkAll(diktatRunnerArguments) + DiktatMode.FIX -> diktatRunner.fixAll(diktatRunnerArguments) { updatedFile -> + log.warn { + "Original and formatted content differ, writing to ${updatedFile.absolutePathString()}..." } } } diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 593880e368..a219c2d6b3 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -1,15 +1,18 @@ package org.cqfn.diktat.cli -import org.cqfn.diktat.api.DiktatReporter +import org.cqfn.diktat.DiktatRunnerArguments +import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.api.DiktatReporterFactory import org.cqfn.diktat.common.config.rules.DIKTAT import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF -import com.pinterest.ktlint.core.Reporter +import org.cqfn.diktat.util.isKotlinCodeOrScript +import org.cqfn.diktat.util.tryToPathIfExists +import org.cqfn.diktat.util.walkByGlob import com.pinterest.ktlint.core.ReporterProvider import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LoggerContext import org.slf4j.event.Level -import java.io.PrintStream +import java.io.OutputStream import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.createDirectories @@ -37,34 +40,6 @@ data class DiktatProperties( private val logLevel: Level, val patterns: List, ) { - /** - * @param diktatReporterFactory - * @param sourceRootDir - * @return a configured [Reporter] - */ - fun reporter( - diktatReporterFactory: DiktatReporterFactory, - sourceRootDir: Path, - ): DiktatReporter { - val outputStream = output - ?.let { Paths.get(it) } - ?.also { it.parent.createDirectories() } - ?.outputStream() - ?.let { PrintStream(it) } - ?: System.out - return if (reporterProviderId == diktatReporterFactory.plainId) { - diktatReporterFactory.createPlain(outputStream, sourceRootDir, colorNameInPlain, groupByFileInPlain) - } else { - require(colorNameInPlain == null) { - "colorization is applicable only for plain reporter" - } - require(!groupByFileInPlain) { - "groupByFile is applicable only for plain reporter" - } - diktatReporterFactory.invoke(reporterProviderId, outputStream, sourceRootDir) - } - } - /** * Configure logger level using [logLevel] */ @@ -84,6 +59,37 @@ data class DiktatProperties( .updateLoggers() } + /** + * @return [DiktatRunnerArguments] created from [DiktatProperties] + */ + fun toRunnerArguments(sourceRootDir: Path, loggingListener: DiktatProcessorListener): DiktatRunnerArguments = DiktatRunnerArguments( + configFileName = config, + sourceRootDir = sourceRootDir, + files = getFiles(sourceRootDir), + baselineFile = null, + reporterType = reporterProviderId, + reporterOutput = getReporterOutput(), + groupByFileInPlain = groupByFileInPlain, + colorNameInPlain = colorNameInPlain, + loggingListener = loggingListener, + ) + + private fun getFiles(sourceRootDir: Path): Collection = patterns + .asSequence() + .flatMap { pattern -> + pattern.tryToPathIfExists()?.let { sequenceOf(it) } + ?: sourceRootDir.walkByGlob(pattern) + } + .filter { file -> file.isKotlinCodeOrScript() } + .distinct() + .map { it.normalize() } + .toList() + + private fun getReporterOutput(): OutputStream? = output + ?.let { Paths.get(it) } + ?.also { it.parent.createDirectories() } + ?.outputStream() + companion object { /** * @param diktatReporterFactory diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt index 1d7c2ca30c..96425526d1 100644 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -63,7 +63,7 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { outputStream: OutputStream, sourceRootDir: Path, colorName: String?, - groupByFile: Boolean, + groupByFile: Boolean?, ): DiktatReporter { val opt = buildMap { colorName?.let { @@ -73,9 +73,7 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { put("color", false) put("color_name", Color.DARK_GRAY) } - if (groupByFile) { - put("group_by_file", true) - } + groupByFile?.let { put("group_by_file", it) } }.mapValues { it.value.toString() } return plainReporterProvider.get(outputStream.asPrintStream(), opt).wrap(sourceRootDir) } 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 b83b0ad615..7a8e3fd402 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 @@ -36,6 +36,6 @@ class LocalCommandExecutor internal constructor(private val command: String) { } companion object { - private val log = KotlinLogging.logger { } + private val log = KotlinLogging.logger {} } } From 93d16b631b143987ec057fb547b892a9df166b8d Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 12:20:16 +0300 Subject: [PATCH 14/34] diktatFix --- diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt | 5 ++++- .../main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt index cce72dd049..bd263b1d71 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -4,16 +4,19 @@ package org.cqfn.diktat -import org.cqfn.diktat.cli.DiktatMode import org.cqfn.diktat.api.DiktatProcessorListener +import org.cqfn.diktat.cli.DiktatMode import org.cqfn.diktat.cli.DiktatProperties import org.cqfn.diktat.ktlint.DiktatBaselineFactoryImpl import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl + import mu.KotlinLogging + import java.nio.file.Path import java.nio.file.Paths + import kotlin.io.path.absolutePathString private val log = KotlinLogging.logger { } diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index a219c2d6b3..91b834a3ac 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -60,9 +60,14 @@ data class DiktatProperties( } /** + * @param sourceRootDir + * @param loggingListener * @return [DiktatRunnerArguments] created from [DiktatProperties] */ - fun toRunnerArguments(sourceRootDir: Path, loggingListener: DiktatProcessorListener): DiktatRunnerArguments = DiktatRunnerArguments( + fun toRunnerArguments( + sourceRootDir: Path, + loggingListener: DiktatProcessorListener, + ): DiktatRunnerArguments = DiktatRunnerArguments( configFileName = config, sourceRootDir = sourceRootDir, files = getFiles(sourceRootDir), From bbe9ac942a4f91f5d464a4b3f870f5928b76c43e Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 13:30:23 +0300 Subject: [PATCH 15/34] fixed --version and --license --- diktat-api/build.gradle.kts | 30 +++++++++++ diktat-cli/build.gradle.kts | 39 +++++++++++++++ .../org/cqfn/diktat/cli/DiktatProperties.kt | 34 +++++++------ diktat-gradle-plugin/build.gradle.kts | 1 - diktat-ktlint-engine/build.gradle.kts | 35 +++++++++++++ diktat-ruleset/build.gradle.kts | 3 +- gradle/libs.versions.toml | 2 + ...ktat-version-file-configuration.gradle.kts | 50 ------------------- 8 files changed, 128 insertions(+), 66 deletions(-) delete mode 100644 gradle/plugins/src/main/kotlin/org/cqfn/diktat/buildutils/diktat-version-file-configuration.gradle.kts diff --git a/diktat-api/build.gradle.kts b/diktat-api/build.gradle.kts index 228b175173..d27314538f 100644 --- a/diktat-api/build.gradle.kts +++ b/diktat-api/build.gradle.kts @@ -9,3 +9,33 @@ project.description = "This module builds diktat-api" dependencies { implementation(libs.kotlin.compiler.embeddable) } + +val generateDiktatVersionFile by tasks.registering { + val outputDir = File("$buildDir/generated/src") + val versionsFile = outputDir.resolve("generated/DiktatVersion.kt") + + val diktatVersion = version.toString() + + inputs.property("diktat version", diktatVersion) + outputs.dir(outputDir) + + doFirst { + versionsFile.parentFile.mkdirs() + versionsFile.writeText( + """ + package generated + + const val DIKTAT_VERSION = "$diktatVersion" + + """.trimIndent() + ) + } +} + +kotlin.sourceSets.getByName("main") { + kotlin.srcDir( + generateDiktatVersionFile.map { + it.outputs.files.singleFile + } + ) +} diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 925e255a3e..48d4e1bb50 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -1,9 +1,12 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + @Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration") alias(libs.plugins.kotlin.plugin.serialization) + alias(libs.plugins.shadow) } project.description = "This module builds diktat-cli to run diktat as CLI using ktlint" @@ -13,4 +16,40 @@ dependencies { implementation(projects.diktatRules) implementation(libs.kotlinx.cli) implementation(libs.log4j2.core) + implementation(libs.log4j2.slf4j) +} + +val addLicenseTask = tasks.register("addLicense") { + val sourceLicenseFile = rootProject.file("LICENSE") + val outputDir = File("$buildDir/generated/src") + val targetDir = outputDir.resolve("META-INF/diktat") + + inputs.file(sourceLicenseFile) + outputs.dir(outputDir) + + copy { + from(rootProject.path) + include(sourceLicenseFile.name) + into(targetDir) + } +} + +kotlin.sourceSets.getByName("main") { + kotlin.srcDir( + addLicenseTask.map { + it.outputs.files.singleFile + } + ) +} + +tasks.named("shadowJar") { + archiveClassifier.set("") + manifest { + attributes["Main-Class"] = "org.cqfn.diktat.DiktatMainKt" + } +} + +// disable default jar +tasks.named("jar") { + enabled = false } diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 91b834a3ac..451021f9c0 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -8,7 +8,8 @@ import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF import org.cqfn.diktat.util.isKotlinCodeOrScript import org.cqfn.diktat.util.tryToPathIfExists import org.cqfn.diktat.util.walkByGlob -import com.pinterest.ktlint.core.ReporterProvider +import generated.DIKTAT_VERSION +import generated.KTLINT_VERSION import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LoggerContext import org.slf4j.event.Level @@ -122,7 +123,7 @@ data class DiktatProperties( shortName = "m", description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." ).default(DiktatMode.CHECK) - val reporterProviderId: String by parser.reporterProviderId(diktatReporterFactory) + val reporterType: String by parser.reporterType(diktatReporterFactory) val output: String? by parser.option( type = ArgType.String, fullName = "output", @@ -147,26 +148,31 @@ data class DiktatProperties( description = "A list of files to process by diktat" ).vararg() - parser.addOptionAndShowResourceWithExit( + parser.addOptionAndShowTextWithExit( fullName = "version", shortName = "V", description = "Output version information and exit.", args = args, - resourceName = "META-INF/diktat/version" - ) - parser.addOptionAndShowResourceWithExit( + ) { + """ + Diktat: $DIKTAT_VERSION + Ktlint: $KTLINT_VERSION + """.trimIndent() + } + parser.addOptionAndShowTextWithExit( fullName = "license", shortName = null, description = "Display the license and exit.", args = args, - resourceName = "META-INF/diktat/LICENSE", - ) + ) { + readFromResource("META-INF/diktat/LICENSE") + } parser.parse(args) return DiktatProperties( config = config, mode = mode, - reporterProviderId = reporterProviderId, + reporterProviderId = reporterType, output = output, groupByFileInPlain = groupByFileInPlain, colorNameInPlain = colorName, @@ -177,9 +183,9 @@ data class DiktatProperties( /** * @param diktatReporterFactory - * @return a single [ReporterProvider] as parsed cli arg + * @return a single type of [org.cqfn.diktat.api.DiktatReporter] as parsed cli arg */ - private fun ArgParser.reporterProviderId(diktatReporterFactory: DiktatReporterFactory) = option( + private fun ArgParser.reporterType(diktatReporterFactory: DiktatReporterFactory) = option( type = ArgType.Choice( choices = diktatReporterFactory.ids.toList(), toVariant = { it }, @@ -206,12 +212,12 @@ data class DiktatProperties( description = "Colorize the output.", ) - private fun ArgParser.addOptionAndShowResourceWithExit( + private fun ArgParser.addOptionAndShowTextWithExit( fullName: String, shortName: String?, description: String, args: Array, - resourceName: String, + contentSupplier: () -> String ) { // add here to print in help option( @@ -222,7 +228,7 @@ data class DiktatProperties( ) if (args.contains("--$fullName") || shortName?.let { args.contains("-$it") } == true) { @Suppress("DEBUG_PRINT", "ForbiddenMethodCall") - print(readFromResource(resourceName)) + println(contentSupplier()) exitProcess(0) } } diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index 02adc921ca..b76bc0e329 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -5,7 +5,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") - id("org.cqfn.diktat.buildutils.diktat-version-file-configuration") id("pl.droidsonroids.jacoco.testkit") version "1.0.9" id("org.gradle.test-retry") version "1.5.2" id("com.gradle.plugin-publish") version "1.1.0" diff --git a/diktat-ktlint-engine/build.gradle.kts b/diktat-ktlint-engine/build.gradle.kts index f7853669be..37088e239b 100644 --- a/diktat-ktlint-engine/build.gradle.kts +++ b/diktat-ktlint-engine/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.accessors.dm.LibrariesForLibs + plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") @@ -24,3 +26,36 @@ dependencies { testImplementation(libs.assertj.core) testImplementation(libs.mockito) } + +val ktlintVersion: String = the() + .versions + .ktlint + .get() + +val generateKtlintVersionFile by tasks.registering { + val outputDir = File("$buildDir/generated/src") + val versionsFile = outputDir.resolve("generated/KtLintVersion.kt") + + inputs.property("ktlint version", ktlintVersion) + outputs.dir(outputDir) + + doFirst { + versionsFile.parentFile.mkdirs() + versionsFile.writeText( + """ + package generated + + const val KTLINT_VERSION = "$ktlintVersion" + + """.trimIndent() + ) + } +} + +kotlin.sourceSets.getByName("main") { + kotlin.srcDir( + generateKtlintVersionFile.map { + it.outputs.files.singleFile + } + ) +} diff --git a/diktat-ruleset/build.gradle.kts b/diktat-ruleset/build.gradle.kts index 58ec49b40d..0e446cffc0 100644 --- a/diktat-ruleset/build.gradle.kts +++ b/diktat-ruleset/build.gradle.kts @@ -2,11 +2,12 @@ import org.cqfn.diktat.buildutils.configurePom import com.github.jengelman.gradle.plugins.shadow.ShadowExtension import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +@Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration") id("org.cqfn.diktat.buildutils.code-quality-convention") id("org.cqfn.diktat.buildutils.publishing-configuration") - id("com.github.johnrengelman.shadow") version "7.1.2" + alias(libs.plugins.shadow) `maven-publish` } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a065481ea9..695c985450 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ mockito-all = "1.10.19" # executable jar kotlinx-cli = "0.3.5" +gradle-shadow = "7.1.2" # copied from save-cloud jetbrains-annotations = "24.0.1" @@ -68,6 +69,7 @@ talaiot-base = { id = "io.github.cdsap.talaiot.plugin.base", version = "1.5.3" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } spotless = { id = "com.diffplug.gradle.spotless", version.ref = "spotless" } download = { id = "de.undercouch.download", version.ref = "download" } +shadow = { id = "com.github.johnrengelman.shadow", version.ref = "gradle-shadow" } [libraries] save-common = { module = "com.saveourtool.save:save-common", version.ref = "save-cli" } diff --git a/gradle/plugins/src/main/kotlin/org/cqfn/diktat/buildutils/diktat-version-file-configuration.gradle.kts b/gradle/plugins/src/main/kotlin/org/cqfn/diktat/buildutils/diktat-version-file-configuration.gradle.kts deleted file mode 100644 index fa5f3267f2..0000000000 --- a/gradle/plugins/src/main/kotlin/org/cqfn/diktat/buildutils/diktat-version-file-configuration.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -package org.cqfn.diktat.buildutils - -import org.gradle.accessors.dm.LibrariesForLibs -import org.gradle.kotlin.dsl.getValue -import org.gradle.kotlin.dsl.kotlin -import org.gradle.kotlin.dsl.provideDelegate -import org.gradle.kotlin.dsl.registering -import org.gradle.kotlin.dsl.the -import java.io.File - -plugins { - kotlin("jvm") -} - -val ktlintVersion: String = the() - .versions - .ktlint - .get() - -val generateVersionsFile by tasks.registering { - val outputDir = File("$buildDir/generated/src") - val versionsFile = outputDir.resolve("generated/Versions.kt") - - val diktatVersion = version.toString() - - inputs.property("diktat version", diktatVersion) - inputs.property("ktlint version", ktlintVersion) - outputs.dir(outputDir) - - doFirst { - versionsFile.parentFile.mkdirs() - versionsFile.writeText( - """ - package generated - - internal const val DIKTAT_VERSION = "$diktatVersion" - internal const val KTLINT_VERSION = "$ktlintVersion" - - """.trimIndent() - ) - } -} - -kotlin.sourceSets.getByName("main") { - kotlin.srcDir( - generateVersionsFile.map { - it.outputs.files.singleFile - } - ) -} From fcd27bfefadb8e07842853d2e875a73c12f51cb8 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 14:12:00 +0300 Subject: [PATCH 16/34] fixed --license --- diktat-cli/build.gradle.kts | 23 +++++++++++-------- .../org/cqfn/diktat/cli/DiktatProperties.kt | 13 +++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 48d4e1bb50..2e29485f81 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.incremental.createDirectory @Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { @@ -19,23 +20,25 @@ dependencies { implementation(libs.log4j2.slf4j) } -val addLicenseTask = tasks.register("addLicense") { - val sourceLicenseFile = rootProject.file("LICENSE") +val addLicenseTask = tasks.register("addLicense") { + val licenseFile = rootProject.file("LICENSE") val outputDir = File("$buildDir/generated/src") - val targetDir = outputDir.resolve("META-INF/diktat") - inputs.file(sourceLicenseFile) + inputs.file(licenseFile) outputs.dir(outputDir) - copy { - from(rootProject.path) - include(sourceLicenseFile.name) - into(targetDir) + doLast { + licenseFile.copyTo( + outputDir.resolve("META-INF").resolve("diktat") + .also { it.createDirectory() } + .resolve(licenseFile.name), + overwrite = true + ) } } -kotlin.sourceSets.getByName("main") { - kotlin.srcDir( +sourceSets.getByName("main") { + resources.srcDir( addLicenseTask.map { it.outputs.files.singleFile } diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 451021f9c0..d7245da64a 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -165,7 +165,12 @@ data class DiktatProperties( description = "Display the license and exit.", args = args, ) { - readFromResource("META-INF/diktat/LICENSE") + val resourceName = "META-INF/diktat/LICENSE" + DiktatProperties::class.java + .classLoader + .getResource(resourceName) + ?.readText() + ?: error("Resource $resourceName not found") } parser.parse(args) @@ -232,11 +237,5 @@ data class DiktatProperties( exitProcess(0) } } - - private fun readFromResource(resourceName: String): String = DiktatProperties::class.java - .classLoader - .getResource(resourceName) - ?.readText() - ?: error("Resource $resourceName not found") } } From 57129b10bc85aaaa1bbc4e53f6f861ac55aaae95 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 14:45:07 +0300 Subject: [PATCH 17/34] supported test using diktat-cli --- .github/workflows/build_and_test.yml | 45 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bdb1df0d75..721d2d0892 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -57,13 +57,20 @@ jobs: name: gradle-reports path: '**/build/reports/' retention-days: 1 - - name: Upload diktat jar + - name: Upload diktat-ruleset jar uses: actions/upload-artifact@v3 with: name: diktat-ruleset path: diktat-ruleset/build/libs/diktat-*.jar # no need to store artifact longer, it is used only by dependant jobs retention-days: 1 + - name: Upload diktat-cli jar + uses: actions/upload-artifact@v3 + with: + name: diktat-cli + path: diktat-cli/build/libs/diktat-cli-*.jar + # no need to store artifact longer, it is used only by dependant jobs + retention-days: 1 - name: Code coverage report uses: codecov/codecov-action@v3 with: @@ -77,37 +84,55 @@ jobs: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] java-version: [8, 11] + type: [ ktlint, cli ] steps: - uses: actions/checkout@v3 - name: Retrieve Ktlint version + if: ${{ runner.type == 'ktlint' }} run: | ktlint_version=$(cat gradle/libs.versions.toml | grep '^ktlint =' | awk -F'[=]' '{print $2}' | tr -d '" ') echo KTLINT_VERSION=$ktlint_version >> $GITHUB_ENV shell: bash - name: Setup environment + if: ${{ runner.type == 'ktlint' }} run: | curl -o ktlint -sSL https://github.com/pinterest/ktlint/releases/download/${{ env.KTLINT_VERSION }}/ktlint && chmod a+x ktlint java -version shell: bash - name: Download diktat jar + if: ${{ runner.type == 'ktlint' }} uses: actions/download-artifact@v3 with: name: diktat-ruleset - - name: Retrieve diktat jar file name + - name: Generate run command using ktlint + if: ${{ runner.type == 'ktlint' }} run: | filename=$(ls diktat-*.jar) - echo DIKTAT_JAR=$filename >> $GITHUB_ENV + echo DIKTAT_RUN="java -jar ktlint -R $filename --disabled_rules=standard,experimental,test,custom" >> $GITHUB_ENV + shell: bash + + - name: Download diktat cli jar + if: ${{ runner.type == 'cli' }} + uses: actions/download-artifact@v3 + with: + name: diktat-cli + + - name: Generate run command using cli + if: ${{ runner.type == 'cli' }} + run: | + filename=$(ls diktat-cli-*.jar) + echo DIKTAT_RUN="java -jar $filename" >> $GITHUB_ENV shell: bash - name: Run diKTat from cli continue-on-error: true run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom 'examples/maven/src/main/kotlin/Test.kt' &>out.txt + ${{ env.DIKTAT_RUN }} 'examples/maven/src/main/kotlin/Test.kt' &>out.txt shell: bash - name: Check output @@ -121,14 +146,14 @@ jobs: continue-on-error: true if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }} run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom "$PWD/examples/maven/src/main/kotlin/Test.kt" &>out.txt + ${{ env.DIKTAT_RUN }} "$PWD/examples/maven/src/main/kotlin/Test.kt" &>out.txt shell: bash - name: Run diKTat from cli on windows (absolute paths) continue-on-error: true if: runner.os == 'Windows' run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom "%cd%/examples/maven/src/main/kotlin/Test.kt" > out.txt 2>&1 + ${{ env.DIKTAT_RUN }} "%cd%/examples/maven/src/main/kotlin/Test.kt" > out.txt 2>&1 shell: cmd - name: Check output (absolute paths) @@ -141,7 +166,7 @@ jobs: - name: Run diKTat from cli (glob paths, 1 of 4) continue-on-error: true run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom 'examples/maven/src/main/kotlin/*.kt' &>out.txt + ${{ env.DIKTAT_RUN }} 'examples/maven/src/main/kotlin/*.kt' &>out.txt shell: bash - name: Check output (glob paths, 1 of 4) @@ -154,7 +179,7 @@ jobs: - name: Run diKTat from cli (glob paths, 2 of 4) continue-on-error: true run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom 'examples/**/main/kotlin/*.kt' &>out.txt + ${{ env.DIKTAT_RUN }} 'examples/**/main/kotlin/*.kt' &>out.txt shell: bash - name: Check output (glob paths, 2 of 4) @@ -167,7 +192,7 @@ jobs: - name: Run diKTat from cli (glob paths, 3 of 4) continue-on-error: true run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom 'examples/**/*.kt' &>out.txt + ${{ env.DIKTAT_RUN }} 'examples/**/*.kt' &>out.txt shell: bash - name: Check output (glob paths, 3 of 4) @@ -180,7 +205,7 @@ jobs: - name: Run diKTat from cli (glob paths, 4 of 4) continue-on-error: true run: | - java -jar ktlint -R ${{ env.DIKTAT_JAR }} --disabled_rules=standard,experimental,test,custom '**/*.kt' &>out.txt + ${{ env.DIKTAT_RUN }} '**/*.kt' &>out.txt shell: bash - name: Check output (glob paths, 4 of 4) From fc1203fdb2ca6e27c5b44a1a8e40d35466f3ed4d Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 14:56:11 +0300 Subject: [PATCH 18/34] fixed checking condition in github's matrix --- .github/workflows/build_and_test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 721d2d0892..dcc004f1f2 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -90,40 +90,40 @@ jobs: - uses: actions/checkout@v3 - name: Retrieve Ktlint version - if: ${{ runner.type == 'ktlint' }} + if: matrix.type == 'ktlint' run: | ktlint_version=$(cat gradle/libs.versions.toml | grep '^ktlint =' | awk -F'[=]' '{print $2}' | tr -d '" ') echo KTLINT_VERSION=$ktlint_version >> $GITHUB_ENV shell: bash - name: Setup environment - if: ${{ runner.type == 'ktlint' }} + if: matrix.type == 'ktlint' run: | curl -o ktlint -sSL https://github.com/pinterest/ktlint/releases/download/${{ env.KTLINT_VERSION }}/ktlint && chmod a+x ktlint java -version shell: bash - name: Download diktat jar - if: ${{ runner.type == 'ktlint' }} + if: matrix.type == 'ktlint' uses: actions/download-artifact@v3 with: name: diktat-ruleset - name: Generate run command using ktlint - if: ${{ runner.type == 'ktlint' }} + if: matrix.type == 'ktlint' run: | filename=$(ls diktat-*.jar) echo DIKTAT_RUN="java -jar ktlint -R $filename --disabled_rules=standard,experimental,test,custom" >> $GITHUB_ENV shell: bash - name: Download diktat cli jar - if: ${{ runner.type == 'cli' }} + if: matrix.type == 'cli' uses: actions/download-artifact@v3 with: name: diktat-cli - name: Generate run command using cli - if: ${{ runner.type == 'cli' }} + if: matrix.type == 'cli' run: | filename=$(ls diktat-cli-*.jar) echo DIKTAT_RUN="java -jar $filename" >> $GITHUB_ENV From f4abea836f922f3d4dfbb37ec9badfc0ec648022 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 15:09:13 +0300 Subject: [PATCH 19/34] fixed build of diktat-cli --- diktat-cli/build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 2e29485f81..9bb27dd64f 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -56,3 +56,10 @@ tasks.named("shadowJar") { tasks.named("jar") { enabled = false } + +// it triggers shadowJar with default build +tasks { + build { + dependsOn(shadowJar) + } +} From 10f8efa14554d2d196d6710589432072a74166eb Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 15:24:45 +0300 Subject: [PATCH 20/34] added dependency to diktat-api explicitly --- diktat-cli/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 9bb27dd64f..18f689f692 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -13,6 +13,7 @@ plugins { project.description = "This module builds diktat-cli to run diktat as CLI using ktlint" dependencies { + implementation(projects.diktatApi) implementation(projects.diktatKtlintEngine) implementation(projects.diktatRules) implementation(libs.kotlinx.cli) From 2f3369b313c65ab26d6ea58b806313c84a16b5f0 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 16:35:59 +0300 Subject: [PATCH 21/34] fixed clash in file name --- diktat-cli/build.gradle.kts | 1 + .../kotlin/org/cqfn/diktat/util/{FileUtils.kt => CliUtils.kt} | 2 +- diktat-ruleset/build.gradle.kts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) rename diktat-cli/src/main/kotlin/org/cqfn/diktat/util/{FileUtils.kt => CliUtils.kt} (98%) diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 18f689f692..0b54613e06 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -51,6 +51,7 @@ tasks.named("shadowJar") { manifest { attributes["Main-Class"] = "org.cqfn.diktat.DiktatMainKt" } + duplicatesStrategy = DuplicatesStrategy.FAIL } // disable default jar diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt similarity index 98% rename from diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt rename to diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 22f5027272..9b8cdf4fa5 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/FileUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -1,5 +1,5 @@ /** - * This class contains util methods to operate with java.nio.file.Path + * This class contains util methods to operate with java.nio.file.Path for CLI */ package org.cqfn.diktat.util diff --git a/diktat-ruleset/build.gradle.kts b/diktat-ruleset/build.gradle.kts index 0e446cffc0..b1b72bbbca 100644 --- a/diktat-ruleset/build.gradle.kts +++ b/diktat-ruleset/build.gradle.kts @@ -47,6 +47,7 @@ tasks.named("shadowJar") { archiveClassifier.set("") // need to relocate serialization from kaml to avoid conflicts with KtLint relocate("kotlinx.serialization", "com.saveourtool.kotlinx_serialization") + duplicatesStrategy = DuplicatesStrategy.FAIL } // disable default jar From 87519061a1e3ff939f4658bf143b5502a71ef00a Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 17:16:35 +0300 Subject: [PATCH 22/34] fixed absolute path resolving --- diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt index bd263b1d71..e7eee241f4 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -42,7 +42,7 @@ fun main(args: Array) { log.debug { "Loading diktatRuleSet using config ${properties.config}" } - val currentFolder = Paths.get(".") + val currentFolder = Paths.get(".").toAbsolutePath() val diktatRunnerArguments = properties.toRunnerArguments( sourceRootDir = currentFolder, loggingListener = loggingListener, From f47e2115513b978a5228dadb6929f346d6bc6283 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 17:32:56 +0300 Subject: [PATCH 23/34] fixed absolute path resolving --- .../src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index d7245da64a..60da69de11 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -89,6 +89,7 @@ data class DiktatProperties( .filter { file -> file.isKotlinCodeOrScript() } .distinct() .map { it.normalize() } + .map { it.toAbsolutePath() } .toList() private fun getReporterOutput(): OutputStream? = output From 863c9568fa1ddc64df9d2af2bfae161ee4c34efc Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 18:42:44 +0300 Subject: [PATCH 24/34] matching by two globs --- .../main/kotlin/org/cqfn/diktat/DiktatMain.kt | 2 +- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt index e7eee241f4..aae16f9407 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt @@ -42,7 +42,7 @@ fun main(args: Array) { log.debug { "Loading diktatRuleSet using config ${properties.config}" } - val currentFolder = Paths.get(".").toAbsolutePath() + val currentFolder = Paths.get(".").toAbsolutePath().normalize() val diktatRunnerArguments = properties.toRunnerArguments( sourceRootDir = currentFolder, loggingListener = loggingListener, diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 9b8cdf4fa5..160f7225ff 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -4,6 +4,7 @@ package org.cqfn.diktat.util +import java.nio.file.FileSystem import java.nio.file.InvalidPathException import java.nio.file.Path import java.nio.file.Paths @@ -19,12 +20,16 @@ import kotlin.io.path.walk * @return a sequence of files which matches to [glob] */ @OptIn(ExperimentalPathApi::class) -fun Path.walkByGlob(glob: String): Sequence = - fileSystem.getPathMatcher("glob:$glob") - .let { matcher -> - this.walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { matcher.matches(it) } - } +fun Path.walkByGlob(glob: String): Sequence = run { + fileSystem.globMatcher(glob) to fileSystem.relativeGlobMatcher(glob) +}.let { (matcher, relativeMatcher) -> + this.walk(PathWalkOption.INCLUDE_DIRECTORIES) + .filter { matcher.matches(it) || relativeMatcher.matches(it) } +} + +private fun FileSystem.globMatcher(glob: String) = getPathMatcher("glob:${glob.replace(java.io.File.separatorChar, '/')}") + +private fun FileSystem.relativeGlobMatcher(glob: String) = globMatcher("**/$glob") /** * @return path or null if path is invalid or doesn't exist From 484edc19902e3c375f44689efac8a8f2021554af Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 18:48:53 +0300 Subject: [PATCH 25/34] diktatFix --- .../src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 160f7225ff..751b154063 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -27,10 +27,6 @@ fun Path.walkByGlob(glob: String): Sequence = run { .filter { matcher.matches(it) || relativeMatcher.matches(it) } } -private fun FileSystem.globMatcher(glob: String) = getPathMatcher("glob:${glob.replace(java.io.File.separatorChar, '/')}") - -private fun FileSystem.relativeGlobMatcher(glob: String) = globMatcher("**/$glob") - /** * @return path or null if path is invalid or doesn't exist */ @@ -39,3 +35,7 @@ fun String.tryToPathIfExists(): Path? = try { } catch (e: InvalidPathException) { null } + +private fun FileSystem.globMatcher(glob: String) = getPathMatcher("glob:${glob.replace(java.io.File.separatorChar, '/')}") + +private fun FileSystem.relativeGlobMatcher(glob: String) = globMatcher("**/$glob") From 32af2fd53ee8de1f114032d0503929c022611464 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 5 Apr 2023 19:29:18 +0300 Subject: [PATCH 26/34] added test for glob walker --- diktat-cli/build.gradle.kts | 5 + .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 23 +++-- .../org/cqfn/diktat/util/CliUtilsKtTest.kt | 92 +++++++++++++++++++ 3 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt diff --git a/diktat-cli/build.gradle.kts b/diktat-cli/build.gradle.kts index 0b54613e06..3f7a3b8528 100644 --- a/diktat-cli/build.gradle.kts +++ b/diktat-cli/build.gradle.kts @@ -19,6 +19,11 @@ dependencies { implementation(libs.kotlinx.cli) implementation(libs.log4j2.core) implementation(libs.log4j2.slf4j) + + testImplementation(libs.junit.jupiter) + testImplementation(libs.junit.platform.suite) + testImplementation(libs.assertj.core) + testImplementation(libs.mockito) } val addLicenseTask = tasks.register("addLicense") { diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 751b154063..3031a2323b 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -7,10 +7,12 @@ package org.cqfn.diktat.util import java.nio.file.FileSystem import java.nio.file.InvalidPathException import java.nio.file.Path +import java.nio.file.PathMatcher import java.nio.file.Paths import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.PathWalkOption import kotlin.io.path.exists +import kotlin.io.path.pathString import kotlin.io.path.walk /** @@ -20,12 +22,11 @@ import kotlin.io.path.walk * @return a sequence of files which matches to [glob] */ @OptIn(ExperimentalPathApi::class) -fun Path.walkByGlob(glob: String): Sequence = run { - fileSystem.globMatcher(glob) to fileSystem.relativeGlobMatcher(glob) -}.let { (matcher, relativeMatcher) -> - this.walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { matcher.matches(it) || relativeMatcher.matches(it) } -} +fun Path.walkByGlob(glob: String): Sequence = fileSystem.globMatcher(glob) + .let { matcher -> + this.walk(PathWalkOption.INCLUDE_DIRECTORIES) + .filter { matcher.matches(it) } + } /** * @return path or null if path is invalid or doesn't exist @@ -36,6 +37,12 @@ fun String.tryToPathIfExists(): Path? = try { null } -private fun FileSystem.globMatcher(glob: String) = getPathMatcher("glob:${glob.replace(java.io.File.separatorChar, '/')}") +private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGlob(glob)) { + getPathMatcher("glob:${glob.toUnixSeparator()}") +} else { + getPathMatcher("glob:**/${glob.toUnixSeparator()}") +} + +private fun FileSystem.isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || rootDirectories.any { glob.startsWith(it.pathString, true) } -private fun FileSystem.relativeGlobMatcher(glob: String) = globMatcher("**/$glob") +private fun String.toUnixSeparator(): String = replace(java.io.File.separatorChar, '/') diff --git a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt new file mode 100644 index 0000000000..d54220ac89 --- /dev/null +++ b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt @@ -0,0 +1,92 @@ +package org.cqfn.diktat.util + +import org.jetbrains.kotlin.konan.file.File +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.createDirectory +import kotlin.io.path.createFile +import kotlin.io.path.writeText + +class CliUtilsKtTest { + private fun setupHierarchy(dir: Path) { + dir.resolveAndCreateDirectory("folder1") + .also { folder1 -> + folder1.resolveAndCreateDirectory("subFolder11") + .also { subFolder11 -> + subFolder11.resolveAndCreateFile("Test1.kt") + subFolder11.resolveAndCreateFile("Test2.kt") + } + folder1.resolveAndCreateDirectory("subFolder12") + .also { subFolder12 -> + subFolder12.resolveAndCreateFile("Test1.kt") + } + } + dir.resolveAndCreateDirectory("folder2") + .also { folder2 -> + folder2.resolveAndCreateFile("Test1.kt") + folder2.resolveAndCreateFile("Test2.kt") + folder2.resolveAndCreateFile("Test3.kt") + } + } + + @Test + fun walkByGlobWithLeadingAsterisks(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + Assertions.assertEquals( + listOf( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), + tmpDir.resolve("folder1").resolve("subFolder12").resolve("Test1.kt"), + tmpDir.resolve("folder2").resolve("Test1.kt"), + ), + tmpDir.walkByGlob("**/Test1.kt").toList() + ) + } + + + @Test + fun walkByGlobWithGlobalPath(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + + Assertions.assertEquals( + listOf( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + tmpDir.resolve("folder2").resolve("Test2.kt"), + ), + tmpDir.walkByGlob("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList() + ) + } + + @Test + fun walkByGlobWithRelativePath(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + Assertions.assertEquals( + listOf( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + ), + tmpDir.walkByGlob("folder1/subFolder11/*.kt").toList() + ) + } + + @Test + fun walkByGlobWithEmptyResult(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + Assertions.assertEquals( + emptyList(), + tmpDir.walkByGlob("**/*.kts").toList() + ) + } + + companion object { + private fun Path.resolveAndCreateDirectory(name: String): Path = resolve(name).also { + it.createDirectory() + } + + private fun Path.resolveAndCreateFile(name: String): Path = resolve(name).also { + it.createFile().writeText("Test file: $name") + } + } +} From 09d905e9b86416d671baeb7271e022cccea19dab Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 12:18:42 +0300 Subject: [PATCH 27/34] review notes --- .github/workflows/build_and_test.yml | 10 +++++----- .../kotlin/org/cqfn/diktat/cli/DiktatProperties.kt | 2 +- .../test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index dcc004f1f2..df9ff07394 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -112,8 +112,8 @@ jobs: - name: Generate run command using ktlint if: matrix.type == 'ktlint' run: | - filename=$(ls diktat-*.jar) - echo DIKTAT_RUN="java -jar ktlint -R $filename --disabled_rules=standard,experimental,test,custom" >> $GITHUB_ENV + filename=$(ls -1 diktat-*.jar | head -n1) + echo DIKTAT_RUN="java -jar ktlint -R \"$filename\" --disabled_rules=standard,experimental,test,custom" >> $GITHUB_ENV shell: bash - name: Download diktat cli jar @@ -125,8 +125,8 @@ jobs: - name: Generate run command using cli if: matrix.type == 'cli' run: | - filename=$(ls diktat-cli-*.jar) - echo DIKTAT_RUN="java -jar $filename" >> $GITHUB_ENV + filename=$(ls -1 diktat-cli-*.jar | head -n1) + echo DIKTAT_RUN="java -jar \"$filename\"" >> $GITHUB_ENV shell: bash - name: Run diKTat from cli @@ -153,7 +153,7 @@ jobs: continue-on-error: true if: runner.os == 'Windows' run: | - ${{ env.DIKTAT_RUN }} "%cd%/examples/maven/src/main/kotlin/Test.kt" > out.txt 2>&1 + ${{ env.DIKTAT_RUN }} "%cd%/examples/maven/src/main/kotlin/Test.kt" &>out.txt shell: cmd - name: Check output (absolute paths) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt index 60da69de11..313a35a8b8 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/cli/DiktatProperties.kt @@ -87,9 +87,9 @@ data class DiktatProperties( ?: sourceRootDir.walkByGlob(pattern) } .filter { file -> file.isKotlinCodeOrScript() } - .distinct() .map { it.normalize() } .map { it.toAbsolutePath() } + .distinct() .toList() private fun getReporterOutput(): OutputStream? = output diff --git a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt index d54220ac89..9fc2305654 100644 --- a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt @@ -1,9 +1,9 @@ package org.cqfn.diktat.util -import org.jetbrains.kotlin.konan.file.File import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir +import java.io.File import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectory From 16ae41bb0b673b6b18e511ec29e64e8092757856 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 12:35:56 +0300 Subject: [PATCH 28/34] added a debug code --- diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 3031a2323b..a88120c9b5 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -11,6 +11,7 @@ import java.nio.file.PathMatcher import java.nio.file.Paths import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.PathWalkOption +import kotlin.io.path.absolutePathString import kotlin.io.path.exists import kotlin.io.path.pathString import kotlin.io.path.walk @@ -43,6 +44,9 @@ private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGl getPathMatcher("glob:**/${glob.toUnixSeparator()}") } -private fun FileSystem.isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || rootDirectories.any { glob.startsWith(it.pathString, true) } +private fun FileSystem.isAbsoluteGlob(glob: String): Boolean { + rootDirectories.forEach { println("rootDirectory: ${it.absolutePathString()}") } + return glob.startsWith("**") || rootDirectories.any { glob.startsWith(it.absolutePathString(), true) } +} private fun String.toUnixSeparator(): String = replace(java.io.File.separatorChar, '/') From be77b21f681c6dd130dac4c67e7208339b8f5054 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 13:15:32 +0300 Subject: [PATCH 29/34] fixed running on Windows --- .github/workflows/build_and_test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index df9ff07394..5042ca3eb8 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -152,8 +152,9 @@ jobs: - name: Run diKTat from cli on windows (absolute paths) continue-on-error: true if: runner.os == 'Windows' + # cannot use '&>out.txt' since it's Windows run: | - ${{ env.DIKTAT_RUN }} "%cd%/examples/maven/src/main/kotlin/Test.kt" &>out.txt + ${{ env.DIKTAT_RUN }} "%cd%/examples/maven/src/main/kotlin/Test.kt" > out.txt 2>&1 shell: cmd - name: Check output (absolute paths) From ba07c84403bc11007ecf9548e9a9e84fbfbe5503 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 13:15:40 +0300 Subject: [PATCH 30/34] added more debug info --- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index a88120c9b5..b70371ae27 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -4,7 +4,9 @@ package org.cqfn.diktat.util +import java.io.File import java.nio.file.FileSystem +import java.nio.file.FileSystems import java.nio.file.InvalidPathException import java.nio.file.Path import java.nio.file.PathMatcher @@ -13,7 +15,6 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.PathWalkOption import kotlin.io.path.absolutePathString import kotlin.io.path.exists -import kotlin.io.path.pathString import kotlin.io.path.walk /** @@ -44,9 +45,28 @@ private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGl getPathMatcher("glob:**/${glob.toUnixSeparator()}") } -private fun FileSystem.isAbsoluteGlob(glob: String): Boolean { - rootDirectories.forEach { println("rootDirectory: ${it.absolutePathString()}") } - return glob.startsWith("**") || rootDirectories.any { glob.startsWith(it.absolutePathString(), true) } +private fun isAbsoluteGlob(glob: String): Boolean { + val fromFile = File.listRoots() + .also { + println("listRoots is empty: ${it.iterator().hasNext()}") + } + .iterator() + .asSequence() + .map { + it.toPath().absolutePathString() + } + .onEach { + println("listRoots: $it") + } + val fromFileSystem = FileSystems.getDefault().rootDirectories + .also { + println("rootDirectory is empty: ${it.iterator().hasNext()}") + } + .asSequence() + .map { it.absolutePathString() } + .onEach { println("rootDirectory: $it") } + + return glob.startsWith("**") || sequenceOf(fromFile, fromFileSystem).flatten().any { glob.startsWith(it, true) } } -private fun String.toUnixSeparator(): String = replace(java.io.File.separatorChar, '/') +private fun String.toUnixSeparator(): String = replace(File.separatorChar, '/') From b779464afa23970be49f3dffa72ba6538674fa4d Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 13:18:04 +0300 Subject: [PATCH 31/34] added debug print for all cases --- diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index b70371ae27..9cd9af8195 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -58,6 +58,7 @@ private fun isAbsoluteGlob(glob: String): Boolean { .onEach { println("listRoots: $it") } + .toList() val fromFileSystem = FileSystems.getDefault().rootDirectories .also { println("rootDirectory is empty: ${it.iterator().hasNext()}") @@ -65,6 +66,7 @@ private fun isAbsoluteGlob(glob: String): Boolean { .asSequence() .map { it.absolutePathString() } .onEach { println("rootDirectory: $it") } + .toList() return glob.startsWith("**") || sequenceOf(fromFile, fromFileSystem).flatten().any { glob.startsWith(it, true) } } From ec7162b783a8e570aa5b062db8dad9b8617d2e2c Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 13:53:10 +0300 Subject: [PATCH 32/34] moved debug print to tests --- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 43 ++++++++----------- .../org/cqfn/diktat/util/CliUtilsKtTest.kt | 38 ++++++++++++++++ 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 9cd9af8195..462907149c 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -17,6 +17,23 @@ import kotlin.io.path.absolutePathString import kotlin.io.path.exists import kotlin.io.path.walk +// all roots +private val roots: Set = run { + sequenceOf( + File.listRoots() + .asSequence() + .map { + it.toPath() + }, + FileSystems.getDefault() + .rootDirectories + .asSequence() + ) + .flatten() + .map { it.absolutePathString() } + .toSet() +} + /** * Create a matcher and return a filter that uses it. * @@ -45,30 +62,6 @@ private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGl getPathMatcher("glob:**/${glob.toUnixSeparator()}") } -private fun isAbsoluteGlob(glob: String): Boolean { - val fromFile = File.listRoots() - .also { - println("listRoots is empty: ${it.iterator().hasNext()}") - } - .iterator() - .asSequence() - .map { - it.toPath().absolutePathString() - } - .onEach { - println("listRoots: $it") - } - .toList() - val fromFileSystem = FileSystems.getDefault().rootDirectories - .also { - println("rootDirectory is empty: ${it.iterator().hasNext()}") - } - .asSequence() - .map { it.absolutePathString() } - .onEach { println("rootDirectory: $it") } - .toList() - - return glob.startsWith("**") || sequenceOf(fromFile, fromFileSystem).flatten().any { glob.startsWith(it, true) } -} +private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } private fun String.toUnixSeparator(): String = replace(File.separatorChar, '/') diff --git a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt index 9fc2305654..28396e0669 100644 --- a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt @@ -1,9 +1,11 @@ package org.cqfn.diktat.util +import mu.KotlinLogging import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File +import java.nio.file.FileSystems import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectory @@ -80,7 +82,43 @@ class CliUtilsKtTest { ) } + @Test + fun notEmptyRoots() { + val fromFile = File.listRoots() + .also { + log.info { "listRoots is empty: ${it.iterator().hasNext()}" } + } + .iterator() + .asSequence() + .map { + it.toPath().absolutePathString() + } + .onEach { + log.info { "listRoots: $it" } + } + .toList() + assert(fromFile.isEmpty()) { + "File.listRoots is not empty: ${fromFile.joinToString()}" + } + val fromFileSystem = FileSystems.getDefault().rootDirectories + .also { + log.info { "rootDirectory is empty: ${it.iterator().hasNext()}" } + } + .asSequence() + .map { it.absolutePathString() } + .onEach { + log.info { "rootDirectory: $it" } + } + .toList() + + assert(fromFileSystem.isEmpty()) { + "FileSystems.getDefault().rootDirectories is not empty: ${fromFileSystem.joinToString()}" + } + } + companion object { + private val log = KotlinLogging.logger {} + private fun Path.resolveAndCreateDirectory(name: String): Path = resolve(name).also { it.createDirectory() } From 031d03bc19f5bf60b1455e9a638a686c9bc549cf Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 14:09:18 +0300 Subject: [PATCH 33/34] fixed test on MacOs + updated debug info --- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 4 +- .../org/cqfn/diktat/util/CliUtilsKtTest.kt | 43 ++++++++----------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 462907149c..9f54dd6f93 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -62,6 +62,6 @@ private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGl getPathMatcher("glob:**/${glob.toUnixSeparator()}") } -private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } - private fun String.toUnixSeparator(): String = replace(File.separatorChar, '/') + +private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } diff --git a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt index 28396e0669..be3855235f 100644 --- a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt @@ -1,7 +1,7 @@ package org.cqfn.diktat.util import mu.KotlinLogging -import org.junit.jupiter.api.Assertions +import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File @@ -37,14 +37,13 @@ class CliUtilsKtTest { @Test fun walkByGlobWithLeadingAsterisks(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertEquals( - listOf( + + Assertions.assertThat(tmpDir.walkByGlob("**/Test1.kt").toList()) + .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), tmpDir.resolve("folder1").resolve("subFolder12").resolve("Test1.kt"), tmpDir.resolve("folder2").resolve("Test1.kt"), - ), - tmpDir.walkByGlob("**/Test1.kt").toList() - ) + ) } @@ -52,34 +51,30 @@ class CliUtilsKtTest { fun walkByGlobWithGlobalPath(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertEquals( - listOf( + Assertions.assertThat(tmpDir.walkByGlob("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) + .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), tmpDir.resolve("folder2").resolve("Test2.kt"), - ), - tmpDir.walkByGlob("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList() - ) + ) } @Test fun walkByGlobWithRelativePath(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertEquals( - listOf( + + Assertions.assertThat(tmpDir.walkByGlob("folder1/subFolder11/*.kt").toList()) + .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), - ), - tmpDir.walkByGlob("folder1/subFolder11/*.kt").toList() - ) + ) } @Test fun walkByGlobWithEmptyResult(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertEquals( - emptyList(), - tmpDir.walkByGlob("**/*.kts").toList() - ) + + Assertions.assertThat(tmpDir.walkByGlob("**/*.kts").toList()) + .isEmpty() } @Test @@ -97,9 +92,7 @@ class CliUtilsKtTest { log.info { "listRoots: $it" } } .toList() - assert(fromFile.isEmpty()) { - "File.listRoots is not empty: ${fromFile.joinToString()}" - } + val fromFileSystem = FileSystems.getDefault().rootDirectories .also { log.info { "rootDirectory is empty: ${it.iterator().hasNext()}" } @@ -111,8 +104,8 @@ class CliUtilsKtTest { } .toList() - assert(fromFileSystem.isEmpty()) { - "FileSystems.getDefault().rootDirectories is not empty: ${fromFileSystem.joinToString()}" + assert(fromFileSystem.isEmpty() && fromFile.isEmpty()) { + "FileSystems.getDefault().rootDirectories is not empty: ${fromFileSystem.joinToString()} and File.listRoots is not empty: ${fromFile.joinToString()}" } } From 0183ae14998cbd452b8d3f036a5351e01adae133 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Thu, 6 Apr 2023 14:22:02 +0300 Subject: [PATCH 34/34] cleanup debug --- .../kotlin/org/cqfn/diktat/util/CliUtils.kt | 20 +++-------- .../org/cqfn/diktat/util/CliUtilsKtTest.kt | 36 ------------------- 2 files changed, 5 insertions(+), 51 deletions(-) diff --git a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt index 9f54dd6f93..ace7196ecf 100644 --- a/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/org/cqfn/diktat/util/CliUtils.kt @@ -18,21 +18,11 @@ import kotlin.io.path.exists import kotlin.io.path.walk // all roots -private val roots: Set = run { - sequenceOf( - File.listRoots() - .asSequence() - .map { - it.toPath() - }, - FileSystems.getDefault() - .rootDirectories - .asSequence() - ) - .flatten() - .map { it.absolutePathString() } - .toSet() -} +private val roots: Set = FileSystems.getDefault() + .rootDirectories + .asSequence() + .map { it.absolutePathString() } + .toSet() /** * Create a matcher and return a filter that uses it. diff --git a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt index be3855235f..19d65f1428 100644 --- a/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/org/cqfn/diktat/util/CliUtilsKtTest.kt @@ -1,11 +1,9 @@ package org.cqfn.diktat.util -import mu.KotlinLogging import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File -import java.nio.file.FileSystems import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectory @@ -77,41 +75,7 @@ class CliUtilsKtTest { .isEmpty() } - @Test - fun notEmptyRoots() { - val fromFile = File.listRoots() - .also { - log.info { "listRoots is empty: ${it.iterator().hasNext()}" } - } - .iterator() - .asSequence() - .map { - it.toPath().absolutePathString() - } - .onEach { - log.info { "listRoots: $it" } - } - .toList() - - val fromFileSystem = FileSystems.getDefault().rootDirectories - .also { - log.info { "rootDirectory is empty: ${it.iterator().hasNext()}" } - } - .asSequence() - .map { it.absolutePathString() } - .onEach { - log.info { "rootDirectory: $it" } - } - .toList() - - assert(fromFileSystem.isEmpty() && fromFile.isEmpty()) { - "FileSystems.getDefault().rootDirectories is not empty: ${fromFileSystem.joinToString()} and File.listRoots is not empty: ${fromFile.joinToString()}" - } - } - companion object { - private val log = KotlinLogging.logger {} - private fun Path.resolveAndCreateDirectory(name: String): Path = resolve(name).also { it.createDirectory() }