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 index 1ac5424ddc..3d4edbc280 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterCreationArguments.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterCreationArguments.kt @@ -12,9 +12,9 @@ import java.nio.file.Path */ sealed interface DiktatReporterCreationArguments { /** - * Identifier of [DiktatReporter] which needs to be created + * Type of [DiktatReporter] which needs to be created */ - val id: String + val reporterType: DiktatReporterType /** * Output for [DiktatReporter] @@ -33,23 +33,23 @@ sealed interface DiktatReporterCreationArguments { companion object { /** - * @param id ID of [DiktatReporter] + * @param reporterType type 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, + * @param colorNameInPlain a color name for colorful output which is applicable for plain ([DiktatReporterType.PLAIN]) reporter only, * `null` means to disable colorization. - * @param groupByFileInPlain a flag `groupByFile` which is applicable for plain ([DiktatReporterFactory.PLAIN_ID]) reporter only. + * @param groupByFileInPlain a flag `groupByFile` which is applicable for plain ([DiktatReporterType.PLAIN]) reporter only. * @return created [DiktatReporter] */ operator fun invoke( - id: String, + reporterType: DiktatReporterType, 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) { + return if (reporterType == DiktatReporterType.PLAIN) { PlainDiktatReporterCreationArguments( outputStreamOrStdout, closeOutputStreamAfterAll, sourceRootDir, colorNameInPlain, groupByFileInPlain ) @@ -61,7 +61,7 @@ sealed interface DiktatReporterCreationArguments { "groupByFile is applicable only for plain reporter" } DiktatReporterCreationArgumentsImpl( - id, outputStreamOrStdout, closeOutputStreamAfterAll, sourceRootDir + reporterType, outputStreamOrStdout, closeOutputStreamAfterAll, sourceRootDir ) } } @@ -69,7 +69,7 @@ sealed interface DiktatReporterCreationArguments { } /** - * Implementation of [DiktatReporterCreationArguments] for [DiktatReporterFactory.PLAIN_ID] + * Implementation of [DiktatReporterCreationArguments] for [DiktatReporterType.PLAIN] * * @property outputStream * @property closeOutputStreamAfterAll @@ -84,17 +84,17 @@ data class PlainDiktatReporterCreationArguments( val colorName: String? = null, val groupByFile: Boolean? = null, ) : DiktatReporterCreationArguments { - override val id: String = DiktatReporterFactory.PLAIN_ID + override val reporterType: DiktatReporterType = DiktatReporterType.PLAIN } /** - * @property id + * @property reporterType * @property outputStream * @property closeOutputStreamAfterAll * @property sourceRootDir */ private data class DiktatReporterCreationArgumentsImpl( - override val id: String, + override val reporterType: DiktatReporterType, override val outputStream: OutputStream, override val closeOutputStreamAfterAll: Boolean, override val sourceRootDir: Path?, 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 a9c27aa959..5384c15f87 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 @@ -6,11 +6,6 @@ typealias DiktatReporter = DiktatProcessorListener * A factory to create [DiktatReporter] */ interface DiktatReporterFactory : Function1 { - /** - * Set of supported IDs, must contain [DiktatReporterFactory.NONE_ID] - */ - val ids: Set - /** * Names of color for plain output */ @@ -23,16 +18,4 @@ interface DiktatReporterFactory : Function1(), fullName = "reporter", shortName = "r", description = "The reporter to use" ) - .default(PLAIN_ID) + .default(DiktatReporterType.PLAIN) /** * @param diktatReporterFactory diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatExtension.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatExtension.kt index a51ed2152f..0828de3819 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatExtension.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatExtension.kt @@ -1,20 +1,33 @@ package com.saveourtool.diktat.plugin.gradle +import com.saveourtool.diktat.plugin.gradle.extension.Reporters +import org.gradle.api.Action +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternSet import java.io.File +import javax.inject.Inject /** * An extension to configure diktat in build.gradle(.kts) file * + * @param objectFactory * @param patternSet */ -open class DiktatExtension( - private val patternSet: PatternSet +open class DiktatExtension @Inject constructor( + objectFactory: ObjectFactory, + private val patternSet: PatternSet, ) { + /** + * All reporters + */ + @get:Internal + val reporters: Reporters = objectFactory.newInstance(Reporters::class.java) + /** * Boolean flag to support `ignoreFailures` property of [VerificationTask]. */ @@ -30,16 +43,6 @@ open class DiktatExtension( */ var githubActions = false - /** - * Type of the reporter to use - */ - var reporter: String = "" - - /** - * Destination for reporter. If empty, will write to stdout. - */ - var output: String = "" - /** * Baseline file, containing a list of errors that will be ignored. * If this file doesn't exist, it will be created on the first invocation. @@ -62,4 +65,11 @@ open class DiktatExtension( fun inputs(action: PatternFilterable.() -> Unit) { action(patternSet) } + + /** + * Configure reporters + * + * @param action configuration lambda for [Reporters] + */ + fun reporters(action: Action): Unit = action.execute(reporters) } diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePlugin.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePlugin.kt index 8ed30418fe..f76a0103c8 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePlugin.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePlugin.kt @@ -10,25 +10,23 @@ import org.gradle.api.tasks.util.PatternSet /** * Plugin that configures diktat and registers tasks to run diktat */ -@Suppress("unused", "MagicNumber") class DiktatGradlePlugin : Plugin { /** * @param project a gradle [Project] that the plugin is applied to */ - @Suppress("TOO_LONG_FUNCTION") override fun apply(project: Project) { val patternSet = PatternSet() val diktatExtension = project.extensions.create( DIKTAT_EXTENSION, DiktatExtension::class.java, - patternSet + patternSet, ).apply { diktatConfigFile = project.rootProject.file("diktat-analysis.yml") } project.registerDiktatCheckTask(diktatExtension, patternSet) project.registerDiktatFixTask(diktatExtension, patternSet) - project.configureMergeReportsTask(diktatExtension) + project.configureMergeReportsTask() } companion object { @@ -37,11 +35,6 @@ class DiktatGradlePlugin : Plugin { */ const val DIKTAT_CHECK_TASK = "diktatCheck" - /** - * DiKTat configuration - */ - const val DIKTAT_CONFIGURATION = "diktat" - /** * DiKTat extension */ @@ -56,10 +49,5 @@ class DiktatGradlePlugin : Plugin { * Name of the task that merges SARIF reports of diktat tasks */ internal const val MERGE_SARIF_REPORTS_TASK_NAME = "mergeDiktatReports" - - /** - * Version of JVM with more strict module system, which requires `add-opens` for kotlin compiler - */ - const val MIN_JVM_REQUIRES_ADD_OPENS = 16 } } diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt deleted file mode 100644 index 25d1093126..0000000000 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.saveourtool.diktat.plugin.gradle - -import org.gradle.api.Task - -/** - * An interface with old name for base class for backward compatibility in plugin configuration - */ -@Deprecated("will be removed in 2.x") -interface DiktatJavaExecTaskBase : Task 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 273f70df71..65259bf542 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 @@ -7,74 +7,18 @@ package com.saveourtool.diktat.plugin.gradle import org.gradle.api.Project -import java.io.File -import java.nio.file.Files -import java.nio.file.Path +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider +import org.gradle.api.reporting.ReportingExtension /** - * @param diktatExtension - * @return returns sourceRootDir as projectDir for sarif report + * @param fileName + * @param extension + * @return default location of report with provided [extension] */ -fun Project.getSourceRootDir(diktatExtension: DiktatExtension): Path? = when { - diktatExtension.githubActions -> projectDir.toPath() - diktatExtension.reporter == "sarif" -> projectDir.toPath() - else -> null -} - -/** - * Create CLI flag to set reporter for ktlint based on [diktatExtension]. - * [DiktatExtension.githubActions] should have higher priority than a custom input. - * - * @param diktatExtension extension of type [DiktatExtension] - * @return CLI flag as string - */ -fun Project.getReporterType(diktatExtension: DiktatExtension): String { - val name = diktatExtension.reporter.trim() - val validReporters = listOf("sarif", "plain", "json", "html") - val reporterType = when { - diktatExtension.githubActions -> { - if (diktatExtension.reporter.isNotEmpty()) { - logger.warn("`diktat.githubActions` is set to true, so custom reporter [$name] will be ignored and SARIF reporter will be used") - } - "sarif" - } - name.isEmpty() -> { - logger.info("Reporter name was not set. Using 'plain' reporter") - "plain" - } - name !in validReporters -> { - logger.warn("Reporter name is invalid (provided value: [$name]). Falling back to 'plain' reporter") - "plain" - } - else -> name - } - - return reporterType -} - -/** - * Get destination file for Diktat report or null if stdout is used. - * [DiktatExtension.githubActions] should have higher priority than a custom input. - * - * @param diktatExtension extension of type [DiktatExtension] - * @return destination [File] or null if stdout is used - */ -internal fun Project.getOutputFile(diktatExtension: DiktatExtension): File? = when { - diktatExtension.githubActions -> project.layout.buildDirectory - .file("reports/diktat/diktat.sarif") - .get() - .asFile - .also { - Files.createDirectories(it.parentFile.toPath()) - } - diktatExtension.output.isNotEmpty() -> file(diktatExtension.output) - else -> null -} - -/** - * Whether SARIF reporter is enabled or not - * - * @param reporterFlag - * @return whether SARIF reporter is enabled - */ -internal fun isSarifReporterActive(reporterFlag: String) = reporterFlag.contains("sarif") +internal fun Project.defaultReportLocation( + extension: String, + fileName: String = "diktat", +): Provider = project.layout + .buildDirectory + .file("${ReportingExtension.DEFAULT_REPORTS_DIR_NAME}/diktat/$fileName.$extension") diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/DefaultReporter.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/DefaultReporter.kt new file mode 100644 index 0000000000..6f55a3d243 --- /dev/null +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/DefaultReporter.kt @@ -0,0 +1,157 @@ +/** + * All default reporters + */ + +@file:Suppress("UnnecessaryAbstractClass") + +package com.saveourtool.diktat.plugin.gradle.extension + +import com.saveourtool.diktat.api.DiktatReporterType +import com.saveourtool.diktat.plugin.gradle.defaultReportLocation +import org.gradle.api.Project +import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Provider +import java.io.File +import javax.inject.Inject + +/** + * A base interface for reporter + * + * @param objectFactory + * @param project + * @property type type of reporter + */ +abstract class DefaultReporter @Inject constructor( + val type: DiktatReporterType, + objectFactory: ObjectFactory, + project: Project, +) : Reporter { + override val output: RegularFileProperty = objectFactory.fileProperty() + .also { fileProperty -> + fileProperty.convention(project.defaultReportLocation(extension = type.extension)) + } +} + +/** + * Plain reporter + * + * @param objectFactory + * @param project + */ +abstract class PlainReporter @Inject constructor( + objectFactory: ObjectFactory, + project: Project, +) : DefaultReporter( + type = DiktatReporterType.PLAIN, + objectFactory, + project, +) { + /** + * Remove the default value for plain to print to stdout by default + */ + override val output: RegularFileProperty = objectFactory.fileProperty() + .also { fileProperty -> + fileProperty.set(null as File?) + } +} + +/** + * JSON reporter + * + * @param objectFactory + * @param project + */ +abstract class JsonReporter @Inject constructor( + objectFactory: ObjectFactory, + project: Project, +) : DefaultReporter( + type = DiktatReporterType.JSON, + objectFactory, + project, +) + +/** + * SARIF reporter + * + * @param objectFactory + * @param project + */ +abstract class SarifReporter @Inject constructor( + objectFactory: ObjectFactory, + project: Project, +) : DefaultReporter( + type = DiktatReporterType.SARIF, + objectFactory, + project, +) + +/** + * GitHub actions reporter + * + * @param objectFactory + * @param project + */ +abstract class GitHubActionsReporter @Inject constructor( + project: Project, + objectFactory: ObjectFactory, +) : SarifReporter(objectFactory, project) { + override val output: RegularFileProperty = objectFactory.fileProperty() + .also { fileProperty -> + fileProperty.convention(project.getGitHubActionReporterOutput()) + .finalizeValue() + } + + /** + * Location for merged output + */ + val mergeOutput: RegularFileProperty = objectFactory.fileProperty() + .also { fileProperty -> + fileProperty.convention(project.getGitHubActionReporterMergeOutput()) + .finalizeValue() + } + + companion object { + /** + * @return [RegularFile] for output + */ + fun Project.getGitHubActionReporterOutput(): Provider = defaultReportLocation(extension = "sarif") + + /** + * @return [RegularFile] for mergeOutput + */ + fun Project.getGitHubActionReporterMergeOutput(): Provider = + rootProject.defaultReportLocation(fileName = "diktat-merged", extension = "sarif") + } +} + +/** + * Checkstyle reporter + * + * @param objectFactory + * @param project + */ +abstract class CheckstyleReporter @Inject constructor( + objectFactory: ObjectFactory, + project: Project, +) : DefaultReporter( + type = DiktatReporterType.CHECKSTYLE, + objectFactory, + project, +) + +/** + * HTML reporter + * + * @param objectFactory + * @param project + */ +abstract class HtmlReporter @Inject constructor( + objectFactory: ObjectFactory, + project: Project, +) : DefaultReporter( + type = DiktatReporterType.HTML, + objectFactory, + project, +) diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/Reporter.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/Reporter.kt new file mode 100644 index 0000000000..2ccb3c0060 --- /dev/null +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/Reporter.kt @@ -0,0 +1,15 @@ +package com.saveourtool.diktat.plugin.gradle.extension + +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.OutputFile + +/** + * A base interface for reporter + */ +interface Reporter { + /** + * Location for output + */ + @get:OutputFile + val output: RegularFileProperty +} diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/Reporters.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/Reporters.kt new file mode 100644 index 0000000000..8dec50d6c1 --- /dev/null +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/extension/Reporters.kt @@ -0,0 +1,99 @@ +package com.saveourtool.diktat.plugin.gradle.extension + +import org.gradle.api.Action +import org.gradle.api.model.ObjectFactory +import javax.inject.Inject + +/** + * Configuration for reporters + */ +open class Reporters @Inject constructor( + private val objectFactory: ObjectFactory, +) { + /** + * All reporters + */ + val all: MutableList = mutableListOf() + + /** + * Configure *plain* reporter with [action] configuration + * + * @param action + */ + fun plain(action: Action): Unit = action.execute(newReporter()) + + /** + * Configure *plain* reporter with default configuration + */ + fun plain() { + plain(emptyAction()) + } + + /** + * Configure *json* reporter with [action] configuration + * + * @param action + */ + fun json(action: Action): Unit = action.execute(newReporter()) + + /** + * Configure *json* reporter with default configuration + */ + fun json() { + json(emptyAction()) + } + + /** + * Configure *sarif* reporter with [action] configuration + * + * @param action + */ + fun sarif(action: Action): Unit = action.execute(newReporter()) + + /** + * Configure *sarif* reporter with default configuration + */ + fun sarif() { + sarif(emptyAction()) + } + + /** + * Configure *sarif* reporter for GitHub actions + */ + fun gitHubActions() { + newReporter() + } + + /** + * Configure *checkstyle* reporter with [action] configuration + * + * @param action + */ + fun checkstyle(action: Action): Unit = action.execute(newReporter()) + + /** + * Configure *checkstyle* reporter with default configuration + */ + fun checkstyle() { + checkstyle(emptyAction()) + } + + /** + * Configure *html* reporter with default configuration + */ + fun html() { + html(emptyAction()) + } + + /** + * Configure *html* reporter with [action] configuration + * + * @param action + */ + fun html(action: Action): Unit = action.execute(newReporter()) + + private inline fun newReporter(): T = objectFactory.newInstance(T::class.java) + .apply { all.add(this) } + + private inline fun emptyAction() = Action { } +} diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatCheckTask.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatCheckTask.kt index 6f656825a8..0bd16a3cc3 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatCheckTask.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatCheckTask.kt @@ -5,6 +5,7 @@ import com.saveourtool.diktat.DiktatRunnerArguments import com.saveourtool.diktat.plugin.gradle.DiktatExtension import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternSet @@ -15,8 +16,13 @@ import javax.inject.Inject */ abstract class DiktatCheckTask @Inject constructor( extension: DiktatExtension, - inputs: PatternFilterable -) : DiktatTaskBase(extension, inputs) { + inputs: PatternFilterable, + objectFactory: ObjectFactory, +) : DiktatTaskBase( + extension, + inputs, + objectFactory +) { override fun doRun( runner: DiktatRunner, args: DiktatRunnerArguments @@ -30,11 +36,11 @@ abstract class DiktatCheckTask @Inject constructor( */ fun Project.registerDiktatCheckTask( diktatExtension: DiktatExtension, - patternSet: PatternSet + patternSet: PatternSet, ): TaskProvider = tasks.register( DiktatGradlePlugin.DIKTAT_CHECK_TASK, DiktatCheckTask::class.java, - diktatExtension, patternSet - ) + diktatExtension, patternSet, + ).also { it.configure(diktatExtension) } } } diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatFixTask.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatFixTask.kt index 0e7fdc4e4e..eeee80a933 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatFixTask.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatFixTask.kt @@ -5,6 +5,7 @@ import com.saveourtool.diktat.DiktatRunnerArguments import com.saveourtool.diktat.plugin.gradle.DiktatExtension import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternSet @@ -15,8 +16,13 @@ import javax.inject.Inject */ abstract class DiktatFixTask @Inject constructor( extension: DiktatExtension, - inputs: PatternFilterable -) : DiktatTaskBase(extension, inputs) { + inputs: PatternFilterable, + objectFactory: ObjectFactory, +) : DiktatTaskBase( + extension, + inputs, + objectFactory +) { override fun doRun( runner: DiktatRunner, args: DiktatRunnerArguments @@ -32,11 +38,11 @@ abstract class DiktatFixTask @Inject constructor( */ fun Project.registerDiktatFixTask( diktatExtension: DiktatExtension, - patternSet: PatternSet + patternSet: PatternSet, ): TaskProvider = tasks.register( DiktatGradlePlugin.DIKTAT_FIX_TASK, DiktatFixTask::class.java, - diktatExtension, patternSet - ) + diktatExtension, patternSet, + ).also { it.configure(diktatExtension) } } } 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 d200f19278..21e8553e2b 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 @@ -5,12 +5,14 @@ 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.api.DiktatReporterType 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.extension.DefaultReporter +import com.saveourtool.diktat.plugin.gradle.extension.PlainReporter +import com.saveourtool.diktat.plugin.gradle.extension.Reporters import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl @@ -18,17 +20,25 @@ import generated.DIKTAT_VERSION import generated.KTLINT_VERSION import org.gradle.api.DefaultTask import org.gradle.api.GradleException +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputFiles import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.VerificationTask import org.gradle.api.tasks.util.PatternFilterable import org.gradle.language.base.plugins.LifecycleBasePlugin +import java.nio.file.Files import java.nio.file.Path @@ -42,7 +52,21 @@ import java.nio.file.Path abstract class DiktatTaskBase( @get:Internal internal val extension: DiktatExtension, private val inputs: PatternFilterable, -) : DefaultTask(), VerificationTask, com.saveourtool.diktat.plugin.gradle.DiktatJavaExecTaskBase { + objectFactory: ObjectFactory, +) : DefaultTask(), VerificationTask { + /** + * Config file + */ + @get:InputFile + abstract val configFile: RegularFileProperty + + /** + * Baseline + */ + @get:Optional + @get:InputFile + abstract val baselineFile: RegularFileProperty + /** * Files that will be analyzed by diktat */ @@ -62,6 +86,23 @@ abstract class DiktatTaskBase( ) } + /** + * All reporters + */ + @get:Internal + val reporters: Reporters = objectFactory.newInstance(Reporters::class.java) + + /** + * Outputs for all reporters + */ + @get:OutputFiles + @get:Optional + val reporterOutputs: ConfigurableFileCollection = objectFactory.fileCollection() + .also { fileCollection -> + fileCollection.setFrom(reporters.all.mapNotNull { it.output.orNull }) + fileCollection.finalizeValue() + } + /** * Whether diktat should be executed */ @@ -85,12 +126,18 @@ abstract class DiktatTaskBase( 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 defaultPlainReporter by lazy { + project.objects.newInstance(PlainReporter::class.java) + } + val reporterCreationArgumentsList = (reporters.all.takeUnless { it.isEmpty() } ?: listOf(defaultPlainReporter)) + .filterIsInstance() + .map { reporter -> + DiktatReporterCreationArguments( + reporterType = reporter.type, + outputStream = reporter.output.map { file -> file.asFile.also { Files.createDirectories(it.parentFile.toPath()) }.outputStream() }.orNull, + sourceRootDir = sourceRootDir.takeIf { reporter.type == DiktatReporterType.SARIF }, + ) + } val loggingListener = object : DiktatProcessorListener { override fun beforeAll(files: Collection) { project.logger.info("Analyzing {} files with diktat in project {}", files.size, project.name) @@ -101,11 +148,11 @@ abstract class DiktatTaskBase( } } DiktatRunnerArguments( - configFile = extension.diktatConfigFile.toPath(), + configInputStream = configFile.get().asFile.inputStream(), sourceRootDir = sourceRootDir, files = actualInputs.files.map { it.toPath() }, - baselineFile = extension.baseline?.let { project.file(it).toPath() }, - reporterArgsList = listOf(reporterCreationArguments), + baselineFile = baselineFile.map { it.asFile.toPath() }.orNull, + reporterArgsList = reporterCreationArgumentsList, loggingListener = loggingListener, ) } @@ -119,7 +166,6 @@ abstract class DiktatTaskBase( } init { - ignoreFailures = extension.ignoreFailures group = LifecycleBasePlugin.VERIFICATION_GROUP } @@ -166,4 +212,21 @@ abstract class DiktatTaskBase( runner: DiktatRunner, args: DiktatRunnerArguments ): Int + + companion object { + /** + * @param extension + */ + fun TaskProvider.configure(extension: DiktatExtension) { + configure { task -> + task.configFile.set(task.project.file(extension.diktatConfigFile)) + extension.baseline?.let { baseline -> task.baselineFile.set(task.project.file(baseline)) } + task.ignoreFailures = extension.ignoreFailures + task.reporters.all.addAll(extension.reporters.all) + if (extension.githubActions) { + task.reporters.gitHubActions() + } + } + } + } } diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/SarifReportMergeTask.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/SarifReportMergeTask.kt index 7991df9f01..972665d383 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/SarifReportMergeTask.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/SarifReportMergeTask.kt @@ -1,10 +1,9 @@ package com.saveourtool.diktat.plugin.gradle.tasks -import com.saveourtool.diktat.plugin.gradle.DiktatExtension +import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin.Companion.DIKTAT_CHECK_TASK import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin.Companion.MERGE_SARIF_REPORTS_TASK_NAME -import com.saveourtool.diktat.plugin.gradle.getOutputFile -import com.saveourtool.diktat.plugin.gradle.getReporterType -import com.saveourtool.diktat.plugin.gradle.isSarifReporterActive +import com.saveourtool.diktat.plugin.gradle.extension.GitHubActionsReporter.Companion.getGitHubActionReporterMergeOutput +import com.saveourtool.diktat.plugin.gradle.extension.GitHubActionsReporter.Companion.getGitHubActionReporterOutput import io.github.detekt.sarif4k.SarifSchema210 import org.gradle.api.DefaultTask @@ -12,6 +11,7 @@ import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity @@ -20,7 +20,6 @@ import org.gradle.api.tasks.TaskExecutionException import org.gradle.api.tasks.VerificationTask import kotlinx.serialization.SerializationException -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -33,12 +32,14 @@ abstract class SarifReportMergeTask : DefaultTask(), VerificationTask { */ @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) + @get:Optional abstract val input: ConfigurableFileCollection /** * Destination for the merged report */ @get:OutputFile + @get:Optional abstract val output: RegularFileProperty /** @@ -46,6 +47,10 @@ abstract class SarifReportMergeTask : DefaultTask(), VerificationTask { */ @TaskAction fun mergeReports() { + if (!output.isPresent) { + logger.debug("Skipping merging SARIF reports because output is not set") + return + } val sarifReports = input.files .filter { it.exists() } .also { logger.info("Merging SARIF reports from files $it") } @@ -80,23 +85,23 @@ abstract class SarifReportMergeTask : DefaultTask(), VerificationTask { } /** - * @param diktatExtension extension of type [DiktatExtension] + * Configure [MERGE_SARIF_REPORTS_TASK_NAME] */ -internal fun Project.configureMergeReportsTask(diktatExtension: DiktatExtension) { - if (path == rootProject.path) { +internal fun Project.configureMergeReportsTask() { + val diktatCheckTask = tasks.named(DIKTAT_CHECK_TASK, DiktatCheckTask::class.java) + val rootMergeSarifReportsTask = if (path == rootProject.path) { tasks.register(MERGE_SARIF_REPORTS_TASK_NAME, SarifReportMergeTask::class.java) { reportMergeTask -> - val mergedReportFile = project.layout.buildDirectory.file("reports/diktat/diktat-merged.sarif") - reportMergeTask.outputs.file(mergedReportFile) - reportMergeTask.output.set(mergedReportFile) + reportMergeTask.output.set(getGitHubActionReporterMergeOutput()) } + } else { + rootProject.tasks.named(MERGE_SARIF_REPORTS_TASK_NAME, SarifReportMergeTask::class.java) } - rootProject.tasks.withType(SarifReportMergeTask::class.java).configureEach { reportMergeTask -> - if (isSarifReporterActive(getReporterType(diktatExtension))) { - getOutputFile(diktatExtension)?.let { reportMergeTask.input.from(it) } - reportMergeTask.shouldRunAfter(tasks.withType(DiktatTaskBase::class.java)) - } + + rootMergeSarifReportsTask.configure { reportMergeTask -> + reportMergeTask.input.from(getGitHubActionReporterOutput()) + reportMergeTask.dependsOn(diktatCheckTask) } - tasks.withType(DiktatTaskBase::class.java).configureEach { diktatJavaExecTaskBase -> - diktatJavaExecTaskBase.finalizedBy(rootProject.tasks.withType(SarifReportMergeTask::class.java)) + diktatCheckTask.configure { + it.finalizedBy(rootMergeSarifReportsTask) } } diff --git a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePluginTest.kt b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePluginTest.kt index 18c9f2a46a..b401c9d5ee 100644 --- a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePluginTest.kt +++ b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatGradlePluginTest.kt @@ -37,13 +37,4 @@ class DiktatGradlePluginTest { Assertions.assertIterableEquals(project.fileTree("src").files, actualInputs.files) Assertions.assertTrue(actualInputs.files.isNotEmpty()) } - - @Test - fun `check default reporter type value`() { - val diktatExtension = project.extensions.getByName("diktat") as DiktatExtension - Assertions.assertEquals("", diktatExtension.reporter) - - val reporterFlag = project.getReporterType(diktatExtension) - Assertions.assertEquals("plain", reporterFlag) - } } 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 c5b4d1430f..22b2d95f90 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 @@ -117,8 +117,11 @@ class DiktatJavaExecTaskTest { assertFiles(emptyList()) { inputs { exclude("*") } diktatConfigFile = project.file("../diktat-analysis.yml") - reporter = "json" - output = "some.txt" + reporters { reportersDsl -> + reportersDsl.json { jsonDsl -> + jsonDsl.output.set(project.layout.buildDirectory.file("some.txt")) + } + } } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask assert(task.diktatRunner.diktatReporter.unwrapFirst() is JsonReporter) @@ -129,7 +132,9 @@ class DiktatJavaExecTaskTest { assertFiles(emptyList()) { inputs { exclude("*") } diktatConfigFile = project.file("../diktat-analysis.yml") - reporter = "json" + reporters { reportersDsl -> + reportersDsl.json() + } } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask assert(task.diktatRunner.diktatReporter.unwrapFirst() is JsonReporter) @@ -156,11 +161,23 @@ class DiktatJavaExecTaskTest { inputs { exclude("*") } diktatConfigFile = project.file("../diktat-analysis.yml") githubActions = true - reporter = "json" - output = "report.json" + reporters { reportersDsl -> + reportersDsl.json { jsonDsl -> + jsonDsl.output.set(project.layout.buildDirectory.file("report.json")) + } + } } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask - assert(task.diktatRunner.diktatReporter.unwrapFirst() is SarifReporter) + val (first, second) = task.diktatRunner.diktatReporter + .unwrap>() + .first() + .unwrap() + .unwrap>() + .toList() + + assert(first.unwrapToKtlint() is JsonReporter) + assert(second.unwrapToKtlint() is SarifReporter) + Assertions.assertEquals( project.rootDir.toString(), System.getProperty("user.home") @@ -172,7 +189,9 @@ class DiktatJavaExecTaskTest { assertFiles(emptyList()) { inputs { exclude("*") } diktatConfigFile = project.file("../diktat-analysis.yml") - reporter = "sarif" + reporters { reportersDsl -> + reportersDsl.sarif() + } } val task = project.tasks.getByName(DIKTAT_CHECK_TASK) as DiktatCheckTask assert(task.diktatRunner.diktatReporter.unwrapFirst() is SarifReporter) diff --git a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/ReporterSelectionTest.kt b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/ReporterSelectionTest.kt deleted file mode 100644 index 3d9d9e71d4..0000000000 --- a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/ReporterSelectionTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.saveourtool.diktat.plugin.gradle - -import org.gradle.api.Project -import org.gradle.api.tasks.util.PatternSet -import org.gradle.testfixtures.ProjectBuilder -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -class ReporterSelectionTest { - private val projectBuilder = ProjectBuilder.builder() - private lateinit var project: Project - - @BeforeEach - fun setUp() { - project = projectBuilder.build() - // mock kotlin sources - project.mkdir("src/main/kotlin") - project.file("src/main/kotlin/Test.kt").createNewFile() - project.pluginManager.apply(DiktatGradlePlugin::class.java) - } - - @Test - fun `should fallback to plain reporter for unknown reporter types`() { - val diktatExtension = DiktatExtension(PatternSet()).apply { - reporter = "jsonx" - } - - Assertions.assertEquals( - "plain", - project.getReporterType(diktatExtension) - ) - } -} 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 1e961caae9..16d2b9a9c2 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 @@ -3,6 +3,7 @@ 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.DiktatReporterType import com.saveourtool.diktat.api.PlainDiktatReporterCreationArguments import com.saveourtool.diktat.ktlint.DiktatReporterImpl.Companion.wrap import com.pinterest.ktlint.cli.reporter.checkstyle.CheckStyleReporterProvider @@ -22,16 +23,13 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { /** * All reporters which __KtLint__ provides */ - private val reporterProviders = setOf( - JsonReporterProvider(), - SarifReporterProvider(), - CheckStyleReporterProvider(), - HtmlReporterProvider(), + private val reporterProviders = mapOf( + DiktatReporterType.JSON to JsonReporterProvider(), + DiktatReporterType.SARIF to SarifReporterProvider(), + DiktatReporterType.CHECKSTYLE to CheckStyleReporterProvider(), + DiktatReporterType.HTML to HtmlReporterProvider(), + DiktatReporterType.PLAIN to plainReporterProvider, ) - .associateBy { it.id } + (DiktatReporterFactory.PLAIN_ID to plainReporterProvider) - - override val ids: Set - get() = reporterProviders.keys override val colorNamesInPlain: Set get() = Color.entries.map { it.name }.toSet() @@ -39,7 +37,7 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { override fun invoke( args: DiktatReporterCreationArguments, ): DiktatReporter { - if (args.id == DiktatReporterFactory.NONE_ID) { + if (args.reporterType == DiktatReporterType.NONE) { return DiktatReporter.empty } val opts = if (args is PlainDiktatReporterCreationArguments) { @@ -53,13 +51,13 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { } args.groupByFile?.let { put("group_by_file", it) } }.mapValues { it.value.toString() } - } else if (args.id == DiktatReporterFactory.PLAIN_ID) { + } else if (args.reporterType == DiktatReporterType.PLAIN) { mapOf("color_name" to Color.DARK_GRAY.name) } else { emptyMap() } - val reporterProvider = reporterProviders[args.id] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") + val reporterProvider = reporterProviders[args.reporterType] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}") if (reporterProvider is SarifReporterProvider) { args.sourceRootDir?.let { System.setProperty("user.home", it.pathString) } } 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 d29f7d954f..f0475f5f7d 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 @@ -4,6 +4,7 @@ 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.api.DiktatReporterType import com.saveourtool.diktat.ktlint.DiktatBaselineFactoryImpl import com.saveourtool.diktat.ktlint.DiktatProcessorFactoryImpl import com.saveourtool.diktat.ktlint.DiktatReporterFactoryImpl @@ -120,11 +121,11 @@ abstract class DiktatBaseMojo : AbstractMojo() { ) val sourceRootDir = mavenProject.basedir.parentFile.toPath() - val reporterId = getReporterType() + val reporterType = getReporterType() val reporterArgs = DiktatReporterCreationArguments( - id = reporterId, + reporterType = reporterType, outputStream = getReporterOutput(), - sourceRootDir = sourceRootDir.takeIf { reporterId == "sarif" }, + sourceRootDir = sourceRootDir.takeIf { reporterType == DiktatReporterType.SARIF }, ) val args = DiktatRunnerArguments( configInputStream = configFile.inputStream(), @@ -143,13 +144,13 @@ abstract class DiktatBaseMojo : AbstractMojo() { } } - private fun getReporterType(): String = if (githubActions) { - "sarif" - } else if (reporter in setOf("sarif", "plain", "json", "html")) { - reporter + private fun getReporterType(): DiktatReporterType = if (githubActions) { + DiktatReporterType.SARIF } else { - log.warn("Reporter name ${this.reporter} was not specified or is invalid. Falling to 'plain' reporter") - "plain" + DiktatReporterType.entries.firstOrNull { it.id.equals(reporter, ignoreCase = true) } ?: run { + log.warn("Reporter name ${this.reporter} was not specified or is invalid. Falling to 'plain' reporter") + DiktatReporterType.PLAIN + } } private fun getReporterOutput(): OutputStream? = if (output.isNotBlank()) {