From 237cb2c7e289e7857eabcedc906566c65d468bdd Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Wed, 26 Jan 2022 17:45:34 +0300 Subject: [PATCH] Gradle and Maven Plugins: reporter types are fixed and updated (#1180) ### What's done: - Fixing gradle plugin reporter to support Sarif - Adding the support for reporters in mvn plugin Co-authored-by: Peter Trifanov --- README.md | 47 +++++++++++++------ .../DiktatGradlePluginFunctionalTest.kt | 2 +- .../diktat/plugin/gradle/DiktatExtension.kt | 2 +- .../plugin/gradle/DiktatJavaExecTaskBase.kt | 32 ++++--------- .../plugin/gradle/DiktatGradlePluginTest.kt | 2 +- .../plugin/gradle/DiktatJavaExecTaskTest.kt | 32 +------------ diktat-maven-plugin/pom.xml | 15 ++++++ .../diktat/plugin/maven/DiktatBaseMojo.kt | 44 ++++++++++++++--- .../maven/DiktatMavenPluginIntegrationTest.kt | 1 + 9 files changed, 101 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 707fbb8226..1c7d41036a 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ Main features of diktat are the following: 4) **Strict detailed coding convention** that you can use in your project. ## Run as CLI-application +
+Download and install binaries: 1. Install KTlint manually: [here](https://github.com/pinterest/ktlint/releases) **OR** use curl: @@ -68,12 +70,34 @@ Main features of diktat are the following: ``` To **autofix** all code style violations use `-F` option. +
+ +## GitHub Native Integration +We suggest everyone to use common ["sarif"](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) format as a `reporterType` in CI/CD. +GitHub has an [integration](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning) +with SARIF format and provides you a native reporting of diktat issues in Pull Requests. + +```text + reporterType = "sarif" + output = "diktat-report.sarif" +``` + +Add the following code to your GitHub Action to upload diktat sarif report (after it was generated). + +```yml + - name: Upload SARIF to Github using the upload-sarif action + uses: github/codeql-action/upload-sarif@v1 + if: ${{ always() }} + with: + sarif_file: build/diktat-report.sarif +``` ## Run with Maven using diktat-maven-plugin This plugin is available since version 0.1.3. You can see how it is configured in our project for self-checks: [pom.xml](pom.xml). If you use it and encounter any problems, feel free to open issues on [github](https://github.com/cqfn/diktat/issues). -Add this plugin to your pom.xml: +
+Add this plugin to your pom.xml: ```xml org.cqfn.diktat @@ -101,6 +125,7 @@ Add this plugin to your pom.xml: ``` +
To run diktat in **only-check** mode use command `$ mvn diktat:check@diktat`. To run diktat in **autocorrect** mode use command `$ mvn diktat:fix@diktat`. @@ -109,7 +134,9 @@ To run diktat in **autocorrect** mode use command `$ mvn diktat:fix@diktat`. Requires a gradle version no lower than 5.3. This plugin is available since version 0.1.5. You can see how the plugin is configured in our examples: [build.gradle.kts](examples/gradle-kotlin-dsl/build.gradle.kts). -Add this plugin to your `build.gradle.kts`: + +
+Add this plugin to your `build.gradle.kts`: ```kotlin plugins { id("org.cqfn.diktat.diktat-gradle-plugin") version "1.0.2" @@ -141,29 +168,21 @@ diktat { } ``` -Also `diktat` extension has different reporters. You can specify `json`, `html`, `checkstyle`, `plain` (default) or your own custom reporter: +Also `diktat` extension has different reporters. You can specify `json`, `html`, `sarif`, `plain` (default) or your own custom reporter (it should be added as a dependency into `diktat` configuration): ```kotlin diktat { - reporter = "json" // "html", "checkstyle", "plain" + reporterType = "json" // "html", "json", "plain" (default), "sarif" } ``` -Example of your custom reporter: -```kotlin -diktat { - reporter = "custom:name:pathToJar" -} -``` -Name parameter is the name of your reporter and as the last parameter you should specify path to jar, which contains your reporter. -[Example of the junit custom reporter.](https://github.com/kryanod/ktlint-junit-reporter) - You can also specify an output. ```kotlin diktat { - reporter = "json" + reporterType = "json" output = "someFile.json" } ``` +
You can run diktat checks using task `diktatCheck` and automatically fix errors with tasks `diktatFix`. diff --git a/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt b/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt index 723368d3eb..1d6f19d9f5 100644 --- a/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt +++ b/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginFunctionalTest.kt @@ -45,7 +45,7 @@ class DiktatGradlePluginFunctionalTest { """${System.lineSeparator()} diktat { inputs { include("src/**/*.kt") } - reporterType = "json" + reporter = "json" output = "test.txt" } """.trimIndent() diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatExtension.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatExtension.kt index abd56fed5e..18ff49ea3f 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatExtension.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatExtension.kt @@ -27,7 +27,7 @@ open class DiktatExtension( /** * Type of the reporter to use */ - var reporterType: String = "plain" + var reporter: String = "plain" /** * Type of output diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt index ca9442ff6a..3b11de7bc4 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt @@ -87,10 +87,6 @@ open class DiktatJavaExecTaskBase @Inject constructor( main = "com.pinterest.ktlint.Main" } - // Plain, checkstyle and json reporter are provided out of the box in ktlint - if (diktatExtension.reporterType == "html") { - diktatConfiguration.dependencies.add(project.dependencies.create("com.pinterest.ktlint:ktlint-reporter-html:$KTLINT_VERSION")) - } classpath = diktatConfiguration project.logger.debug("Setting diktatCheck classpath to ${diktatConfiguration.dependencies.toSet()}") if (diktatExtension.debug) { @@ -155,13 +151,8 @@ open class DiktatJavaExecTaskBase @Inject constructor( private fun createReporterFlag(diktatExtension: DiktatExtension): String { val flag: StringBuilder = StringBuilder() - // Plain, checkstyle and json reporter are provided out of the box in ktlint - when (diktatExtension.reporterType) { - "json" -> flag.append("--reporter=json") - "html" -> flag.append("--reporter=html") - "checkstyle" -> flag.append("--reporter=checkstyle") - else -> customReporter(diktatExtension, flag) - } + // appending the flag with the reporter + setReporter(diktatExtension, flag) if (diktatExtension.output.isNotEmpty()) { flag.append(",output=${diktatExtension.output}") @@ -170,19 +161,14 @@ open class DiktatJavaExecTaskBase @Inject constructor( return flag.toString() } - private fun customReporter(diktatExtension: DiktatExtension, flag: java.lang.StringBuilder) { - if (diktatExtension.reporterType.startsWith("custom")) { - val name = diktatExtension.reporterType.split(":")[1] - val jarPath = diktatExtension.reporterType.split(":")[2] - if (name.isEmpty() || jarPath.isEmpty()) { - project.logger.warn("Either name or path to jar is not specified. Falling to plain reporter") - flag.append("--reporter=plain") - } else { - flag.append("--reporter=$name,artifact=$jarPath") - } - } else { + private fun setReporter(diktatExtension: DiktatExtension, flag: java.lang.StringBuilder) { + val name = diktatExtension.reporter.trim() + val validReporters = listOf("sarif", "plain", "json", "html") + if (name.isEmpty() || !validReporters.contains(name)) { + project.logger.warn("Reporter name $name was not specified or is invalid. Falling to 'plain' reporter") flag.append("--reporter=plain") - project.logger.debug("Unknown reporter was specified. Falling back to plain reporter.") + } else { + flag.append("--reporter=$name") } } diff --git a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt index 439b8ee823..b332471742 100644 --- a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt +++ b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt @@ -37,6 +37,6 @@ class DiktatGradlePluginTest { @Test fun `check that the right reporter dependency added`() { val diktatExtension = project.extensions.getByName("diktat") as DiktatExtension - Assertions.assertTrue(diktatExtension.reporterType == "plain") + Assertions.assertTrue(diktatExtension.reporter == "plain") } } diff --git a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt index 0d3710723d..146c4d68e7 100644 --- a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt +++ b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt @@ -94,7 +94,7 @@ class DiktatJavaExecTaskTest { ) { inputs { exclude("*") } diktatConfigFile = project.file("../diktat-analysis.yml") - reporterType = "json" + reporter = "json" output = "some.txt" } } @@ -106,38 +106,10 @@ class DiktatJavaExecTaskTest { ) { inputs { exclude("*") } diktatConfigFile = project.file("../diktat-analysis.yml") - reporterType = "json" + reporter = "json" } } - @Test - fun `check command line has custom reporter type with output`() { - assertCommandLineEquals( - listOf(null, "--reporter=customName,artifact=customPath") - ) { - inputs { exclude("*") } - diktatConfigFile = project.file("../diktat-analysis.yml") - reporterType = "custom:customName:customPath" - } - } - - @Test - fun `check that project has html dependency`() { - val task = project.registerDiktatTask { - inputs { exclude("*") } - diktatConfigFile = project.file("../diktat-analysis.yml") - reporterType = "html" - } - - Assertions.assertTrue( - project - .configurations - .getByName("diktat") - .dependencies - .any { it.name == "ktlint-reporter-html" }) - Assertions.assertEquals(File(project.projectDir.parentFile, "diktat-analysis.yml").absolutePath, task.systemProperties[DIKTAT_CONF_PROPERTY]) - } - @Test fun `check system property with multiproject build with default config`() { setupMultiProject() diff --git a/diktat-maven-plugin/pom.xml b/diktat-maven-plugin/pom.xml index 5fab75ffaf..b8dba2f2f9 100644 --- a/diktat-maven-plugin/pom.xml +++ b/diktat-maven-plugin/pom.xml @@ -54,6 +54,21 @@ ktlint-reporter-plain ${ktlint.version} + + com.pinterest.ktlint + ktlint-reporter-sarif + ${ktlint.version} + + + com.pinterest.ktlint + ktlint-reporter-json + ${ktlint.version} + + + com.pinterest.ktlint + ktlint-reporter-html + ${ktlint.version} + org.junit.vintage junit-vintage-engine diff --git a/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt b/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt index 88b87fe277..d0408ecd4e 100644 --- a/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt +++ b/diktat-maven-plugin/src/main/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojo.kt @@ -4,9 +4,13 @@ import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.core.Reporter import com.pinterest.ktlint.core.RuleExecutionException import com.pinterest.ktlint.core.RuleSet +import com.pinterest.ktlint.reporter.html.HtmlReporter +import com.pinterest.ktlint.reporter.json.JsonReporter import com.pinterest.ktlint.reporter.plain.PlainReporter +import com.pinterest.ktlint.reporter.sarif.SarifReporter import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugin.MojoExecutionException import org.apache.maven.plugin.MojoFailureException @@ -14,6 +18,8 @@ import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject import java.io.File +import java.io.FileOutputStream +import java.io.PrintStream /** * Base [Mojo] for checking and fixing code using diktat @@ -25,8 +31,19 @@ abstract class DiktatBaseMojo : AbstractMojo() { @Parameter(property = "diktat.debug") var debug = false - // FixMe: Reporter should be chosen via plugin configuration - private val reporter = PlainReporter(System.out) + /** + * Type of the reporter to use + */ + @Parameter(property = "diktat.reporter") + var reporter = "plain" + + /** + * Type of output + * Default: System.out + */ + @Parameter(property = "diktat.output") + var output = "" + private lateinit var reporterImpl: Reporter /** * Path to diktat yml config file. Can be either absolute or relative to project's root directory. @@ -64,6 +81,7 @@ abstract class DiktatBaseMojo : AbstractMojo() { * @throws MojoExecutionException if [RuleExecutionException] has been thrown */ override fun execute() { + reporterImpl = resolveReporter() val configFile = resolveConfig() if (!File(configFile).exists()) { throw MojoExecutionException("Configuration file $diktatConfigFile doesn't exist") @@ -83,12 +101,26 @@ abstract class DiktatBaseMojo : AbstractMojo() { checkDirectory(it, lintErrors, ruleSets) } - reporter.afterAll() + reporterImpl.afterAll() if (lintErrors.isNotEmpty()) { throw MojoFailureException("There are ${lintErrors.size} lint errors") } } + private fun resolveReporter(): Reporter { + val output = if (this.output.isBlank()) System.`out` else PrintStream(FileOutputStream(this.output, true)) + return when (this.reporter) { + "sarif" -> SarifReporter(output) + "plain" -> PlainReporter(output) + "json" -> JsonReporter(output) + "html" -> HtmlReporter(output) + else -> { + log.warn("Reporter name ${this.reporter} was not specified or is invalid. Falling to 'plain' reporter") + PlainReporter(output) + } + } + } + /** * Function that searches diktat config file in maven project hierarchy. * If [diktatConfigFile] is absolute, it's path is used. If [diktatConfigFile] is relative, this method looks for it in all maven parent projects. @@ -128,9 +160,9 @@ abstract class DiktatBaseMojo : AbstractMojo() { .forEach { file -> log.debug("Checking file $file") try { - reporter.before(file.path) + reporterImpl.before(file.path) checkFile(file, lintErrors, ruleSets) - reporter.after(file.path) + reporterImpl.after(file.path) } catch (e: RuleExecutionException) { log.error("Unhandled exception during rule execution: ", e) throw MojoExecutionException("Unhandled exception during rule execution", e) @@ -151,7 +183,7 @@ abstract class DiktatBaseMojo : AbstractMojo() { userData = mapOf("file_path" to file.path), script = file.extension.equals("kts", ignoreCase = true), cb = { lintError, isCorrected -> - reporter.onLintError(file.path, lintError, isCorrected) + reporterImpl.onLintError(file.path, lintError, isCorrected) lintErrors.add(lintError) }, debug = debug diff --git a/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatMavenPluginIntegrationTest.kt b/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatMavenPluginIntegrationTest.kt index 231273b66b..dd44337614 100644 --- a/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatMavenPluginIntegrationTest.kt +++ b/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatMavenPluginIntegrationTest.kt @@ -46,6 +46,7 @@ class DiktatMavenPluginIntegrationTest { Assertions.assertTrue(result.isFailure) val mavenLog = result.mavenLog.stdout.readText() + Assertions.assertTrue( mavenLog.contains(Regex("""Original and formatted content differ, writing to [:\w/\\]+Test\.kt\.\.\.""")) )