Skip to content

Commit

Permalink
Supported multiple reporters in diktat runner (#1803)
Browse files Browse the repository at this point in the history
- refactored `DiktatProcessorListener` to support a several reporters at once
  • Loading branch information
nulls authored Nov 16, 2023
1 parent 0c7ed43 commit 81a7429
Show file tree
Hide file tree
Showing 17 changed files with 370 additions and 224 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ out/
*.sarif

# a generated file for github action
/gradle/libs.versions.toml_snapshot
/gradle/libs.versions.toml_backup
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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())
}

Expand Down
13 changes: 3 additions & 10 deletions diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunner.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand All @@ -35,8 +29,7 @@ data class DiktatRunner(
diktatProcessor,
DiktatProcessorListener(
args.loggingListener,
diktatReporter.skipKnownErrors(diktatBaseline),
diktatBaselineGenerator,
diktatReporter,
errorCounter.countErrorsAsProcessorListener()
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -13,42 +13,30 @@ 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(
val configInputStream: InputStream,
val sourceRootDir: Path?,
val files: Collection<Path>,
val baselineFile: Path?,
val reporterType: String,
val reporterOutput: OutputStream?,
val groupByFileInPlain: Boolean? = null,
val colorNameInPlain: String? = null,
val reporterArgsList: List<DiktatReporterCreationArguments> = emptyList(),
val loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty,
) {
constructor(
configFile: Path,
sourceRootDir: Path?,
files: Collection<Path>,
baselineFile: Path?,
reporterType: String,
reporterOutput: OutputStream?,
groupByFileInPlain: Boolean? = null,
colorNameInPlain: String? = null,
reporterArgsList: List<DiktatReporterCreationArguments> = emptyList(),
loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty,
) : this(
configFile.inputStream(),
sourceRootDir,
files,
baselineFile,
reporterType,
reporterOutput,
groupByFileInPlain,
colorNameInPlain,
reporterArgsList,
loggingListener,
)
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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),
)
}

Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.saveourtool.diktat.api

import com.saveourtool.diktat.util.DiktatProcessorListenerWrapper
import java.nio.file.Path

/**
Expand All @@ -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<DiktatProcessorListener>(
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<Path>) = 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<Path>) = 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()
}
}
}
Original file line number Diff line number Diff line change
@@ -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<DiktatProcessorListener>

/**
* A listener for [com.saveourtool.diktat.DiktatProcessor]
*/
Expand Down Expand Up @@ -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<Path>) = 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<DiktatProcessorListenerIterable>(listeners) {
override fun doBeforeAll(wrappedValue: DiktatProcessorListenerIterable, files: Collection<Path>) = 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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 81a7429

Please sign in to comment.