diff --git a/.gitignore b/.gitignore index 1d8b85b767..87dfd162e9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ out/ *.sarif # a generated file for github action -/gradle/libs.versions.toml_snapshot +/gradle/libs.versions.toml_backup diff --git a/build.gradle.kts b/build.gradle.kts index b3566af866..83c0b37d01 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import org.jetbrains.kotlin.incremental.createDirectory import java.nio.file.Files +import java.nio.file.StandardCopyOption @Suppress("DSL_SCOPE_VIOLATION", "RUN_IN_SCRIPT") // https://github.com/gradle/gradle/issues/22797 plugins { @@ -60,7 +61,7 @@ tasks.create("generateLibsForDiktatSnapshot") { .let { val libsFileForDiktatSnapshot = dir.resolve(libsFileName) Files.write(libsFileForDiktatSnapshot.toPath(), it) - Files.move(libsFile.toPath(), libsFileBackup.toPath()) + Files.move(libsFile.toPath(), libsFileBackup.toPath(), StandardCopyOption.REPLACE_EXISTING) Files.copy(libsFileForDiktatSnapshot.toPath(), libsFile.toPath()) } diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunner.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunner.kt index de2503758c..8a1605b809 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunner.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunner.kt @@ -1,7 +1,5 @@ package com.saveourtool.diktat -import com.saveourtool.diktat.api.DiktatBaseline -import com.saveourtool.diktat.api.DiktatBaseline.Companion.skipKnownErrors import com.saveourtool.diktat.api.DiktatProcessorListener import com.saveourtool.diktat.api.DiktatProcessorListener.Companion.countErrorsAsProcessorListener import com.saveourtool.diktat.api.DiktatReporter @@ -15,15 +13,11 @@ private typealias RunAction = (DiktatProcessor, DiktatProcessorListener) -> Unit /** * A runner for diktat on bunch of files using baseline and reporter * - * @param diktatBaselineGenerator - * @property diktatProcessor - * @property diktatBaseline + * @param diktatProcessor * @property diktatReporter */ data class DiktatRunner( - val diktatProcessor: DiktatProcessor, - val diktatBaseline: DiktatBaseline, - private val diktatBaselineGenerator: DiktatProcessorListener, + private val diktatProcessor: DiktatProcessor, val diktatReporter: DiktatReporter, ) { private fun doRun( @@ -35,8 +29,7 @@ data class DiktatRunner( diktatProcessor, DiktatProcessorListener( args.loggingListener, - diktatReporter.skipKnownErrors(diktatBaseline), - diktatBaselineGenerator, + diktatReporter, errorCounter.countErrorsAsProcessorListener() ), ) diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt index 727f74f7ef..fb8db04854 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt @@ -1,8 +1,8 @@ package com.saveourtool.diktat import com.saveourtool.diktat.api.DiktatProcessorListener +import com.saveourtool.diktat.api.DiktatReporterCreationArguments import java.io.InputStream -import java.io.OutputStream import java.nio.file.Path import kotlin.io.path.inputStream @@ -13,10 +13,7 @@ import kotlin.io.path.inputStream * @property sourceRootDir a common root dir for all provided [files] * @property files a collection of files which needs to be fixed * @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 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 reporterArgsList list of arguments to create reporters to report result * @property loggingListener listener to log diktat runner phases, [DiktatProcessorListener.empty] by default */ data class DiktatRunnerArguments( @@ -24,10 +21,7 @@ data class DiktatRunnerArguments( val sourceRootDir: Path?, val files: Collection, val baselineFile: Path?, - val reporterType: String, - val reporterOutput: OutputStream?, - val groupByFileInPlain: Boolean? = null, - val colorNameInPlain: String? = null, + val reporterArgsList: List = emptyList(), val loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty, ) { constructor( @@ -35,20 +29,14 @@ data class DiktatRunnerArguments( sourceRootDir: Path?, files: Collection, baselineFile: Path?, - reporterType: String, - reporterOutput: OutputStream?, - groupByFileInPlain: Boolean? = null, - colorNameInPlain: String? = null, + reporterArgsList: List = emptyList(), loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty, ) : this( configFile.inputStream(), sourceRootDir, files, baselineFile, - reporterType, - reporterOutput, - groupByFileInPlain, - colorNameInPlain, + reporterArgsList, loggingListener, ) } diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt index bd9477c029..85c917fdf2 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt @@ -1,6 +1,7 @@ package com.saveourtool.diktat import com.saveourtool.diktat.api.DiktatBaseline +import com.saveourtool.diktat.api.DiktatBaseline.Companion.skipKnownErrors import com.saveourtool.diktat.api.DiktatBaselineFactory import com.saveourtool.diktat.api.DiktatProcessorListener import com.saveourtool.diktat.api.DiktatReporter @@ -9,7 +10,6 @@ import com.saveourtool.diktat.api.DiktatRuleConfig import com.saveourtool.diktat.api.DiktatRuleConfigReader import com.saveourtool.diktat.api.DiktatRuleSet import com.saveourtool.diktat.api.DiktatRuleSetFactory -import java.io.OutputStream import java.nio.file.Path /** @@ -37,16 +37,14 @@ class DiktatRunnerFactory( val diktatRuleSet = diktatRuleSetFactory(diktatRuleConfigs) val processor = diktatProcessorFactory(diktatRuleSet) val (baseline, baselineGenerator) = resolveBaseline(args.baselineFile, args.sourceRootDir) - val reporter = resolveReporter( - args.reporterType, args.reporterOutput, - args.colorNameInPlain, args.groupByFileInPlain, - args.sourceRootDir - ) + + val reporter = args.reporterArgsList + .map { diktatReporterFactory(it) } + .let { DiktatReporter.union(it) } + return DiktatRunner( diktatProcessor = processor, - diktatBaseline = baseline, - diktatBaselineGenerator = baselineGenerator, - diktatReporter = reporter, + diktatReporter = DiktatReporter(reporter.skipKnownErrors(baseline), baselineGenerator), ) } @@ -62,25 +60,4 @@ class DiktatRunnerFactory( } ?: DiktatProcessorListener.empty DiktatBaseline.empty to baselineGenerator } - - private fun resolveReporter( - reporterType: String, - reporterOutput: OutputStream?, - colorNameInPlain: String?, - groupByFileInPlain: Boolean?, - sourceRootDir: Path?, - ): DiktatReporter { - val (outputStream, closeOutputStream) = reporterOutput?.let { it to true } ?: (System.`out` to false) - return if (reporterType == diktatReporterFactory.plainId) { - diktatReporterFactory.createPlain(outputStream, closeOutputStream, 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, closeOutputStream, sourceRootDir) - } - } } diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatBaseline.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatBaseline.kt index f415801708..2fbe97833e 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatBaseline.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatBaseline.kt @@ -1,5 +1,6 @@ package com.saveourtool.diktat.api +import com.saveourtool.diktat.util.DiktatProcessorListenerWrapper import java.nio.file.Path /** @@ -22,21 +23,24 @@ fun interface DiktatBaseline { * @param baseline * @return wrapped [DiktatProcessorListener] which skips known errors based on [baseline] */ - fun DiktatProcessorListener.skipKnownErrors(baseline: DiktatBaseline): DiktatProcessorListener = object : DiktatProcessorListener { - override fun onError( + fun DiktatProcessorListener.skipKnownErrors(baseline: DiktatBaseline): DiktatProcessorListener = object : DiktatProcessorListenerWrapper( + this@skipKnownErrors + ) { + override fun doOnError( + wrappedValue: DiktatProcessorListener, file: Path, error: DiktatError, isCorrected: Boolean ) { if (!baseline.errorsByFile(file).contains(error)) { - this@skipKnownErrors.onError(file, error, isCorrected) + wrappedValue.onError(file, error, isCorrected) } } - override fun beforeAll(files: Collection) = this@skipKnownErrors.beforeAll(files) - override fun before(file: Path) = this@skipKnownErrors.before(file) - override fun after(file: Path) = this@skipKnownErrors.after(file) - override fun afterAll() = this@skipKnownErrors.afterAll() + override fun doBeforeAll(wrappedValue: DiktatProcessorListener, files: Collection) = wrappedValue.beforeAll(files) + override fun doBefore(wrappedValue: DiktatProcessorListener, file: Path) = wrappedValue.before(file) + override fun doAfter(wrappedValue: DiktatProcessorListener, file: Path) = wrappedValue.after(file) + override fun doAfterAll(wrappedValue: DiktatProcessorListener) = wrappedValue.afterAll() } } } diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatProcessorListener.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatProcessorListener.kt index 5da4aa2404..35c202804d 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatProcessorListener.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatProcessorListener.kt @@ -1,8 +1,11 @@ package com.saveourtool.diktat.api +import com.saveourtool.diktat.util.DiktatProcessorListenerWrapper import java.nio.file.Path import java.util.concurrent.atomic.AtomicInteger +private typealias DiktatProcessorListenerIterable = Iterable + /** * A listener for [com.saveourtool.diktat.DiktatProcessor] */ @@ -57,18 +60,25 @@ interface DiktatProcessorListener { * @param listeners * @return a single [DiktatProcessorListener] which uses all provided [listeners] */ - operator fun invoke(vararg listeners: DiktatProcessorListener): DiktatProcessorListener = object : DiktatProcessorListener { - override fun beforeAll(files: Collection) = listeners.forEach { it.beforeAll(files) } - override fun before(file: Path) = listeners.forEach { it.before(file) } - override fun onError( + fun union(listeners: DiktatProcessorListenerIterable): DiktatProcessorListener = object : DiktatProcessorListenerWrapper(listeners) { + override fun doBeforeAll(wrappedValue: DiktatProcessorListenerIterable, files: Collection) = wrappedValue.forEach { it.beforeAll(files) } + override fun doBefore(wrappedValue: DiktatProcessorListenerIterable, file: Path) = wrappedValue.forEach { it.before(file) } + override fun doOnError( + wrappedValue: DiktatProcessorListenerIterable, 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) + ) = wrappedValue.forEach { it.onError(file, error, isCorrected) } + override fun doAfter(wrappedValue: DiktatProcessorListenerIterable, file: Path) = wrappedValue.forEach { it.after(file) } + override fun doAfterAll(wrappedValue: DiktatProcessorListenerIterable) = wrappedValue.forEach(DiktatProcessorListener::afterAll) } + /** + * @param listeners + * @return a single [DiktatProcessorListener] which uses all provided [listeners] + */ + operator fun invoke(vararg listeners: DiktatProcessorListener): DiktatProcessorListener = union(listeners.asIterable()) + /** * @return An implementation of [DiktatProcessorListener] which counts [DiktatError]s */ diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterCreationArguments.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterCreationArguments.kt new file mode 100644 index 0000000000..1ac5424ddc --- /dev/null +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterCreationArguments.kt @@ -0,0 +1,101 @@ +/** + * Contains a base interface and implementations for a container with arguments to create a reporter + */ + +package com.saveourtool.diktat.api + +import java.io.OutputStream +import java.nio.file.Path + +/** + * Arguments to create [DiktatReporter] using [DiktatReporterFactory] + */ +sealed interface DiktatReporterCreationArguments { + /** + * Identifier of [DiktatReporter] which needs to be created + */ + val id: String + + /** + * Output for [DiktatReporter] + */ + val outputStream: OutputStream + + /** + * Should [outputStream] be closed af the end of [DiktatReporter] + */ + val closeOutputStreamAfterAll: Boolean + + /** + * Directory to base source root to report relative paths in [DiktatReporter] + */ + val sourceRootDir: Path? + + companion object { + /** + * @param id ID of [DiktatReporter] + * @param outputStream stdout will be used when it's empty + * @param sourceRootDir a dir to detect relative path for processing files + * @param colorNameInPlain a color name for colorful output which is applicable for plain ([DiktatReporterFactory.PLAIN_ID]) reporter only, + * `null` means to disable colorization. + * @param groupByFileInPlain a flag `groupByFile` which is applicable for plain ([DiktatReporterFactory.PLAIN_ID]) reporter only. + * @return created [DiktatReporter] + */ + operator fun invoke( + id: String, + outputStream: OutputStream?, + sourceRootDir: Path?, + colorNameInPlain: String? = null, + groupByFileInPlain: Boolean? = null, + ): DiktatReporterCreationArguments { + val (outputStreamOrStdout, closeOutputStreamAfterAll) = outputStream?.let { it to true } ?: (System.`out` to false) + return if (id == DiktatReporterFactory.PLAIN_ID) { + PlainDiktatReporterCreationArguments( + outputStreamOrStdout, closeOutputStreamAfterAll, sourceRootDir, colorNameInPlain, groupByFileInPlain + ) + } else { + require(colorNameInPlain == null) { + "colorization is applicable only for plain reporter" + } + require(groupByFileInPlain == null) { + "groupByFile is applicable only for plain reporter" + } + DiktatReporterCreationArgumentsImpl( + id, outputStreamOrStdout, closeOutputStreamAfterAll, sourceRootDir + ) + } + } + } +} + +/** + * Implementation of [DiktatReporterCreationArguments] for [DiktatReporterFactory.PLAIN_ID] + * + * @property outputStream + * @property closeOutputStreamAfterAll + * @property sourceRootDir + * @property colorName name of color for colorful output, `null` means to disable colorization. + * @property groupByFile + */ +data class PlainDiktatReporterCreationArguments( + override val outputStream: OutputStream, + override val closeOutputStreamAfterAll: Boolean, + override val sourceRootDir: Path?, + val colorName: String? = null, + val groupByFile: Boolean? = null, +) : DiktatReporterCreationArguments { + override val id: String = DiktatReporterFactory.PLAIN_ID +} + +/** + * @property id + * @property outputStream + * @property closeOutputStreamAfterAll + * @property sourceRootDir + */ +private data class DiktatReporterCreationArgumentsImpl( + override val id: String, + override val outputStream: OutputStream, + override val closeOutputStreamAfterAll: Boolean, + override val sourceRootDir: Path?, +) : DiktatReporterCreationArguments diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterFactory.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterFactory.kt index 57b493d8ef..a9c27aa959 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterFactory.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterFactory.kt @@ -1,56 +1,38 @@ package com.saveourtool.diktat.api -import java.io.OutputStream -import java.nio.file.Path - typealias DiktatReporter = DiktatProcessorListener /** * A factory to create [DiktatReporter] */ -interface DiktatReporterFactory : Function4 { +interface DiktatReporterFactory : Function1 { /** - * Set of supported IDs + * Set of supported IDs, must contain [DiktatReporterFactory.NONE_ID] */ 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 closeOutputStreamAfterAll close [outputStream] in [DiktatProcessorListener.afterAll] - * @param sourceRootDir a dir to detect relative path for processing files + * @param args * @return created [DiktatReporter] */ override operator fun invoke( - id: String, - outputStream: OutputStream, - closeOutputStreamAfterAll: Boolean, - sourceRootDir: Path?, + args: DiktatReporterCreationArguments, ): DiktatReporter - /** - * @param outputStream - * @param closeOutputStreamAfterAll close [outputStream] in [DiktatProcessorListener.afterAll] - * @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, - closeOutputStreamAfterAll: Boolean, - sourceRootDir: Path?, - colorName: String? = null, - groupByFile: Boolean? = null, - ): DiktatReporter + companion object { + /** + * ID of [DiktatReporter] for disabled reporter + */ + const val NONE_ID: String = "none" + + /** + * ID of [DiktatReporter] for plain output + */ + const val PLAIN_ID: String = "plain" + } } diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/util/DiktatProcessorListenerWrapper.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/util/DiktatProcessorListenerWrapper.kt new file mode 100644 index 0000000000..e78ceae879 --- /dev/null +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/util/DiktatProcessorListenerWrapper.kt @@ -0,0 +1,89 @@ +package com.saveourtool.diktat.util + +import com.saveourtool.diktat.api.DiktatError +import com.saveourtool.diktat.api.DiktatProcessorListener +import java.nio.file.Path + +/** + * A common wrapper for [DiktatProcessorListener] + * + * @property wrappedValue + */ +open class DiktatProcessorListenerWrapper( + val wrappedValue: T, +) : DiktatProcessorListener { + override fun beforeAll(files: Collection): Unit = doBeforeAll(wrappedValue, files) + + /** + * Called once, before [com.saveourtool.diktat.DiktatProcessor] starts process a bunch of files. + * + * @param wrappedValue + * @param files + */ + protected open fun doBeforeAll(wrappedValue: T, files: Collection): Unit = Unit + + override fun before(file: Path): Unit = doBefore(wrappedValue, file) + + /** + * Called before each file when [com.saveourtool.diktat.DiktatProcessor] starts to process it. + * + * @param wrappedValue + * @param file + */ + protected open fun doBefore(wrappedValue: T, file: Path): Unit = Unit + + override fun onError( + file: Path, + error: DiktatError, + isCorrected: Boolean + ): Unit = doOnError(wrappedValue, file, error, isCorrected) + + /** + * Called on each error when [com.saveourtool.diktat.DiktatProcessor] detects such one. + * + * @param wrappedValue + * @param file + * @param error + * @param isCorrected + */ + protected open fun doOnError( + wrappedValue: T, + file: Path, + error: DiktatError, + isCorrected: Boolean + ): Unit = Unit + + override fun after(file: Path): Unit = doAfter(wrappedValue, file) + + /** + * Called after each file when [com.saveourtool.diktat.DiktatProcessor] finished to process it. + * + * @param wrappedValue + * @param file + */ + protected open fun doAfter(wrappedValue: T, file: Path): Unit = Unit + + override fun afterAll(): Unit = doAfterAll(wrappedValue) + + /** + * Called once, after the processing of [com.saveourtool.diktat.DiktatProcessor] finished. + * + * @param wrappedValue + */ + protected open fun doAfterAll(wrappedValue: T): Unit = Unit + + companion object { + /** + * @return wrapped value [T] if it's possible + */ + inline fun DiktatProcessorListener.tryUnwrap(): T? = (this as? DiktatProcessorListenerWrapper<*>) + ?.wrappedValue + ?.let { it as? T } + + /** + * @return wrapped value [T] or an error + */ + inline fun DiktatProcessorListener.unwrap(): T = tryUnwrap() + ?: error("Unsupported wrapper of ${DiktatProcessorListener::class.java.simpleName} to ${T::class.simpleName}: ${this.javaClass.name}") + } +} diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt index ad388681bb..49a2b74393 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt @@ -2,7 +2,9 @@ package com.saveourtool.diktat.cli import com.saveourtool.diktat.DiktatRunnerArguments import com.saveourtool.diktat.api.DiktatProcessorListener +import com.saveourtool.diktat.api.DiktatReporterCreationArguments import com.saveourtool.diktat.api.DiktatReporterFactory +import com.saveourtool.diktat.api.DiktatReporterFactory.Companion.PLAIN_ID import com.saveourtool.diktat.common.config.rules.DIKTAT import com.saveourtool.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF import com.saveourtool.diktat.util.isKotlinCodeOrScript @@ -72,17 +74,23 @@ data class DiktatProperties( fun toRunnerArguments( sourceRootDir: Path, loggingListener: DiktatProcessorListener, - ): DiktatRunnerArguments = DiktatRunnerArguments( - configInputStream = Paths.get(config).inputStream(), - sourceRootDir = sourceRootDir, - files = getFiles(sourceRootDir), - baselineFile = null, - reporterType = reporterProviderId, - reporterOutput = getReporterOutput(), - groupByFileInPlain = groupByFileInPlain, - colorNameInPlain = colorNameInPlain, - loggingListener = loggingListener, - ) + ): DiktatRunnerArguments { + val reporterCreationArguments = DiktatReporterCreationArguments( + id = reporterProviderId, + outputStream = getReporterOutput(), + groupByFileInPlain = groupByFileInPlain, + colorNameInPlain = colorNameInPlain, + sourceRootDir = sourceRootDir, + ) + return DiktatRunnerArguments( + configInputStream = Paths.get(config).inputStream(), + sourceRootDir = sourceRootDir, + files = getFiles(sourceRootDir), + baselineFile = null, + reporterArgsList = listOf(reporterCreationArguments), + loggingListener = loggingListener + ) + } private fun getFiles(sourceRootDir: Path): Collection = patterns .asSequence() @@ -205,7 +213,7 @@ data class DiktatProperties( shortName = "r", description = "The reporter to use" ) - .default(diktatReporterFactory.plainId) + .default(PLAIN_ID) /** * @param diktatReporterFactory diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/Utils.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/Utils.kt index 609b106787..273f70df71 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/Utils.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/Utils.kt @@ -6,40 +6,11 @@ package com.saveourtool.diktat.plugin.gradle -import groovy.lang.Closure import org.gradle.api.Project import java.io.File import java.nio.file.Files import java.nio.file.Path -@Suppress( - "MISSING_KDOC_TOP_LEVEL", - "MISSING_KDOC_CLASS_ELEMENTS", - "KDOC_NO_CONSTRUCTOR_PROPERTY", - "MISSING_KDOC_ON_FUNCTION", - "KDOC_WITHOUT_PARAM_TAG", - "KDOC_WITHOUT_RETURN_TAG" -) -class KotlinClosure1( - val function: T.() -> V?, - owner: Any? = null, - thisObject: Any? = null -) : Closure(owner, thisObject) { - @Suppress("unused") // to be called dynamically by Groovy - fun doCall(it: T): V? = it.function() -} - -// These two are copy-pasted from `kotlin-dsl` plugin's groovy interop. -// Because `kotlin-dsl` depends on kotlin 1.3.x. -@Suppress( - "MISSING_KDOC_TOP_LEVEL", - "MISSING_KDOC_ON_FUNCTION", - "KDOC_WITHOUT_PARAM_TAG", - "KDOC_WITHOUT_RETURN_TAG" -) -fun Any.closureOf(action: T.() -> Unit): Closure = - KotlinClosure1(action, this, this) - /** * @param diktatExtension * @return returns sourceRootDir as projectDir for sarif report diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt index 8d7148d7d0..d200f19278 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt @@ -4,13 +4,13 @@ import com.saveourtool.diktat.DiktatRunner import com.saveourtool.diktat.DiktatRunnerArguments import com.saveourtool.diktat.DiktatRunnerFactory import com.saveourtool.diktat.api.DiktatProcessorListener +import com.saveourtool.diktat.api.DiktatReporterCreationArguments import com.saveourtool.diktat.ktlint.DiktatBaselineFactoryImpl import com.saveourtool.diktat.ktlint.DiktatProcessorFactoryImpl import com.saveourtool.diktat.ktlint.DiktatReporterFactoryImpl import com.saveourtool.diktat.plugin.gradle.DiktatExtension import com.saveourtool.diktat.plugin.gradle.getOutputFile import com.saveourtool.diktat.plugin.gradle.getReporterType -import com.saveourtool.diktat.plugin.gradle.getSourceRootDir import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl @@ -28,6 +28,7 @@ import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.VerificationTask import org.gradle.api.tasks.util.PatternFilterable +import org.gradle.language.base.plugins.LifecycleBasePlugin import java.nio.file.Path @@ -40,7 +41,7 @@ import java.nio.file.Path @Suppress("WRONG_NEWLINES", "Deprecation") abstract class DiktatTaskBase( @get:Internal internal val extension: DiktatExtension, - private val inputs: PatternFilterable + private val inputs: PatternFilterable, ) : DefaultTask(), VerificationTask, com.saveourtool.diktat.plugin.gradle.DiktatJavaExecTaskBase { /** * Files that will be analyzed by diktat @@ -68,32 +69,44 @@ abstract class DiktatTaskBase( internal val shouldRun: Boolean by lazy { !actualInputs.isEmpty } + private val diktatReporterFactory by lazy { + DiktatReporterFactoryImpl() + } private val diktatRunnerFactory by lazy { DiktatRunnerFactory( diktatRuleConfigReader = DiktatRuleConfigReaderImpl(), diktatRuleSetFactory = DiktatRuleSetFactoryImpl(), diktatProcessorFactory = DiktatProcessorFactoryImpl(), diktatBaselineFactory = DiktatBaselineFactoryImpl(), - diktatReporterFactory = DiktatReporterFactoryImpl() + diktatReporterFactory = diktatReporterFactory, ) } private val diktatRunnerArguments by lazy { + val sourceRootDir by lazy { + project.rootProject.projectDir.toPath() + } + val reporterId = project.getReporterType(extension) + val reporterCreationArguments = DiktatReporterCreationArguments( + id = reporterId, + outputStream = project.getOutputFile(extension)?.outputStream(), + sourceRootDir = sourceRootDir.takeIf { reporterId == "sarif" }, + ) + val loggingListener = object : DiktatProcessorListener { + override fun beforeAll(files: Collection) { + project.logger.info("Analyzing {} files with diktat in project {}", files.size, project.name) + project.logger.debug("Analyzing {}", files) + } + override fun before(file: Path) { + project.logger.debug("Checking file {}", file) + } + } DiktatRunnerArguments( configFile = extension.diktatConfigFile.toPath(), - sourceRootDir = project.getSourceRootDir(extension), + sourceRootDir = sourceRootDir, files = actualInputs.files.map { it.toPath() }, baselineFile = extension.baseline?.let { project.file(it).toPath() }, - reporterType = project.getReporterType(extension), - reporterOutput = project.getOutputFile(extension)?.outputStream(), - loggingListener = object : DiktatProcessorListener { - override fun beforeAll(files: Collection) { - project.logger.info("Analyzing {} files with diktat in project {}", files.size, project.name) - project.logger.debug("Analyzing {}", files) - } - override fun before(file: Path) { - project.logger.debug("Checking file {}", file) - } - } + reporterArgsList = listOf(reporterCreationArguments), + loggingListener = loggingListener, ) } @@ -107,6 +120,7 @@ abstract class DiktatTaskBase( init { ignoreFailures = extension.ignoreFailures + group = LifecycleBasePlugin.VERIFICATION_GROUP } /** diff --git a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt index 2e89333cd9..c5b4d1430f 100644 --- a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt +++ b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt @@ -1,7 +1,9 @@ package com.saveourtool.diktat.plugin.gradle -import com.saveourtool.diktat.ktlint.DiktatReporterImpl.Companion.unwrap +import com.saveourtool.diktat.api.DiktatReporter +import com.saveourtool.diktat.ktlint.DiktatReporterImpl.Companion.unwrapToKtlint import com.saveourtool.diktat.plugin.gradle.tasks.DiktatCheckTask +import com.saveourtool.diktat.util.DiktatProcessorListenerWrapper.Companion.unwrap import com.pinterest.ktlint.cli.reporter.json.JsonReporter import com.pinterest.ktlint.cli.reporter.plain.PlainReporter import com.pinterest.ktlint.cli.reporter.sarif.SarifReporter @@ -55,7 +57,7 @@ class DiktatJavaExecTaskTest { } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is PlainReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is PlainReporter) } @Test @@ -82,7 +84,7 @@ class DiktatJavaExecTaskTest { } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is PlainReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is PlainReporter) } @Test @@ -119,7 +121,7 @@ class DiktatJavaExecTaskTest { output = "some.txt" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is JsonReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is JsonReporter) } @Test @@ -130,7 +132,7 @@ class DiktatJavaExecTaskTest { reporter = "json" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is JsonReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is JsonReporter) } @Test @@ -141,7 +143,7 @@ class DiktatJavaExecTaskTest { githubActions = true } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is SarifReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is SarifReporter) Assertions.assertEquals( project.rootDir.toString(), System.getProperty("user.home") @@ -158,7 +160,7 @@ class DiktatJavaExecTaskTest { output = "report.json" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is SarifReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is SarifReporter) Assertions.assertEquals( project.rootDir.toString(), System.getProperty("user.home") @@ -173,9 +175,9 @@ class DiktatJavaExecTaskTest { reporter = "sarif" } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrap() is SarifReporter) + assert(task.diktatRunner.diktatReporter.unwrapFirst() is SarifReporter) Assertions.assertEquals( - project.rootDir.toString(), + project.rootProject.projectDir.toPath().toString(), System.getProperty("user.home") ) } @@ -262,5 +264,11 @@ class DiktatJavaExecTaskTest { companion object { private const val DIKTAT_CHECK_TASK = "diktatCheck" + + private fun DiktatReporter.unwrapFirst() = this + .unwrap>().first() + .unwrap() + .unwrap>().first() + .unwrapToKtlint() } } diff --git a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt index 19f899ee68..1e961caae9 100644 --- a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -1,7 +1,9 @@ package com.saveourtool.diktat.ktlint import com.saveourtool.diktat.api.DiktatReporter +import com.saveourtool.diktat.api.DiktatReporterCreationArguments import com.saveourtool.diktat.api.DiktatReporterFactory +import com.saveourtool.diktat.api.PlainDiktatReporterCreationArguments import com.saveourtool.diktat.ktlint.DiktatReporterImpl.Companion.wrap import com.pinterest.ktlint.cli.reporter.checkstyle.CheckStyleReporterProvider import com.pinterest.ktlint.cli.reporter.html.HtmlReporterProvider @@ -9,8 +11,6 @@ import com.pinterest.ktlint.cli.reporter.json.JsonReporterProvider import com.pinterest.ktlint.cli.reporter.plain.Color import com.pinterest.ktlint.cli.reporter.plain.PlainReporterProvider import com.pinterest.ktlint.cli.reporter.sarif.SarifReporterProvider -import java.io.OutputStream -import java.nio.file.Path import kotlin.io.path.pathString /** @@ -23,58 +23,46 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { * All reporters which __KtLint__ provides */ private val reporterProviders = setOf( - plainReporterProvider, JsonReporterProvider(), SarifReporterProvider(), CheckStyleReporterProvider(), HtmlReporterProvider(), ) - .associateBy { it.id } + .associateBy { it.id } + (DiktatReporterFactory.PLAIN_ID to plainReporterProvider) override val ids: Set get() = reporterProviders.keys - override val plainId: String - get() = plainReporterProvider.id - override val colorNamesInPlain: Set get() = Color.entries.map { it.name }.toSet() override fun invoke( - id: String, - outputStream: OutputStream, - closeOutputStreamAfterAll: Boolean, - sourceRootDir: Path?, + args: DiktatReporterCreationArguments, ): DiktatReporter { - val reporterProvider = reporterProviders[id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") - if (reporterProvider is SarifReporterProvider) { - sourceRootDir?.let { System.setProperty("user.home", it.pathString) } + if (args.id == DiktatReporterFactory.NONE_ID) { + return DiktatReporter.empty } - val opt = if (reporterProvider is PlainReporterProvider) { + val opts = if (args is PlainDiktatReporterCreationArguments) { + buildMap { + args.colorName?.let { + put("color", true) + put("color_name", it) + } ?: run { + put("color", false) + put("color_name", Color.DARK_GRAY) + } + args.groupByFile?.let { put("group_by_file", it) } + }.mapValues { it.value.toString() } + } else if (args.id == DiktatReporterFactory.PLAIN_ID) { mapOf("color_name" to Color.DARK_GRAY.name) } else { emptyMap() } - return reporterProvider.get(outputStream, closeOutputStreamAfterAll, opt).wrap(sourceRootDir) - } - override fun createPlain( - outputStream: OutputStream, - closeOutputStreamAfterAll: Boolean, - 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) - } - groupByFile?.let { put("group_by_file", it) } - }.mapValues { it.value.toString() } - return plainReporterProvider.get(outputStream, closeOutputStreamAfterAll, opt).wrap(sourceRootDir) + val reporterProvider = reporterProviders[args.id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") + if (reporterProvider is SarifReporterProvider) { + args.sourceRootDir?.let { System.setProperty("user.home", it.pathString) } + } + return reporterProvider.get(args.outputStream, args.closeOutputStreamAfterAll, opts).wrap(args.sourceRootDir) } } diff --git a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterImpl.kt b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterImpl.kt index 8f15b7002d..153941a59c 100644 --- a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterImpl.kt @@ -3,6 +3,7 @@ package com.saveourtool.diktat.ktlint import com.saveourtool.diktat.api.DiktatError import com.saveourtool.diktat.api.DiktatReporter import com.saveourtool.diktat.ktlint.ReporterV2Wrapper.Companion.unwrapIfNeeded +import com.saveourtool.diktat.util.DiktatProcessorListenerWrapper import com.pinterest.ktlint.cli.reporter.core.api.ReporterV2 import java.nio.file.Path @@ -13,18 +14,19 @@ import java.nio.file.Path * @param sourceRootDir */ class DiktatReporterImpl( - private val ktLintReporter: ReporterV2, + ktLintReporter: ReporterV2, private val sourceRootDir: Path?, -) : DiktatReporter { - override fun beforeAll(files: Collection): Unit = ktLintReporter.beforeAll() - override fun before(file: Path): Unit = ktLintReporter.before(file.relativePathStringTo(sourceRootDir)) - override fun onError( +) : DiktatProcessorListenerWrapper(ktLintReporter) { + override fun doBeforeAll(wrappedValue: ReporterV2, files: Collection): Unit = wrappedValue.beforeAll() + override fun doBefore(wrappedValue: ReporterV2, file: Path): Unit = wrappedValue.before(file.relativePathStringTo(sourceRootDir)) + override fun doOnError( + wrappedValue: ReporterV2, file: Path, error: DiktatError, isCorrected: Boolean, - ): Unit = ktLintReporter.onLintError(file.relativePathStringTo(sourceRootDir), error.toKtLintForCli()) - override fun after(file: Path): Unit = ktLintReporter.after(file.relativePathStringTo(sourceRootDir)) - override fun afterAll(): Unit = ktLintReporter.afterAll() + ): Unit = wrappedValue.onLintError(file.relativePathStringTo(sourceRootDir), error.toKtLintForCli()) + override fun doAfter(wrappedValue: ReporterV2, file: Path): Unit = wrappedValue.after(file.relativePathStringTo(sourceRootDir)) + override fun doAfterAll(wrappedValue: ReporterV2): Unit = wrappedValue.afterAll() companion object { /** @@ -36,7 +38,6 @@ class DiktatReporterImpl( /** * @return __KtLint__'s [ReporterV2] */ - fun DiktatReporter.unwrap(): ReporterV2 = (this as? DiktatReporterImpl)?.ktLintReporter?.unwrapIfNeeded() - ?: error("Unsupported wrapper of ${DiktatReporter::class.java.simpleName}: ${this::class.java.canonicalName}") + fun DiktatReporter.unwrapToKtlint(): ReporterV2 = unwrap().unwrapIfNeeded() } } diff --git a/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt b/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt index d02dfdf45e..d29f7d954f 100644 --- a/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt +++ b/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt @@ -3,6 +3,7 @@ package com.saveourtool.diktat.plugin.maven import com.saveourtool.diktat.DiktatRunner import com.saveourtool.diktat.DiktatRunnerArguments import com.saveourtool.diktat.DiktatRunnerFactory +import com.saveourtool.diktat.api.DiktatReporterCreationArguments import com.saveourtool.diktat.ktlint.DiktatBaselineFactoryImpl import com.saveourtool.diktat.ktlint.DiktatProcessorFactoryImpl import com.saveourtool.diktat.ktlint.DiktatReporterFactoryImpl @@ -55,6 +56,18 @@ abstract class DiktatBaseMojo : AbstractMojo() { */ @Parameter(property = "diktat.baseline") var baseline: File? = null + private val diktatReporterFactory by lazy { + DiktatReporterFactoryImpl() + } + private val diktatRunnerFactory by lazy { + DiktatRunnerFactory( + diktatRuleConfigReader = DiktatRuleConfigReaderImpl(), + diktatRuleSetFactory = DiktatRuleSetFactoryImpl(), + diktatProcessorFactory = DiktatProcessorFactoryImpl(), + diktatBaselineFactory = DiktatBaselineFactoryImpl(), + diktatReporterFactory = diktatReporterFactory, + ) + } /** * Path to diktat yml config file. Can be either absolute or relative to project's root directory. @@ -107,20 +120,18 @@ abstract class DiktatBaseMojo : AbstractMojo() { ) val sourceRootDir = mavenProject.basedir.parentFile.toPath() - val diktatRunnerFactory = DiktatRunnerFactory( - diktatRuleConfigReader = DiktatRuleConfigReaderImpl(), - diktatRuleSetFactory = DiktatRuleSetFactoryImpl(), - diktatProcessorFactory = DiktatProcessorFactoryImpl(), - diktatBaselineFactory = DiktatBaselineFactoryImpl(), - diktatReporterFactory = DiktatReporterFactoryImpl() + val reporterId = getReporterType() + val reporterArgs = DiktatReporterCreationArguments( + id = reporterId, + outputStream = getReporterOutput(), + sourceRootDir = sourceRootDir.takeIf { reporterId == "sarif" }, ) val args = DiktatRunnerArguments( configInputStream = configFile.inputStream(), sourceRootDir = sourceRootDir, files = inputs.map(::Path), baselineFile = baseline?.toPath(), - reporterType = getReporterType(), - reporterOutput = getReporterOutput(), + reporterArgsList = listOf(reporterArgs), ) val diktatRunner = diktatRunnerFactory(args) val errorCounter = runAction(