Skip to content

Commit

Permalink
Gradle and Maven Plugins: reporter types are fixed and updated (#1180)
Browse files Browse the repository at this point in the history
### What's done:
- Fixing gradle plugin reporter to support Sarif
- Adding the support for reporters in mvn plugin

Co-authored-by: Peter Trifanov <peter.trifanov@mail.ru>
  • Loading branch information
orchestr7 and petertrr authored Jan 26, 2022
1 parent 3de4c44 commit 237cb2c
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 76 deletions.
47 changes: 33 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<details>
<summary>Download and install binaries:</summary>
1. Install KTlint manually: [here](https://github.com/pinterest/ktlint/releases)

**OR** use curl:
Expand All @@ -68,12 +70,34 @@ Main features of diktat are the following:
```

To **autofix** all code style violations use `-F` option.
</details>

## 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:
<details>
<summary>Add this plugin to your pom.xml:</summary>
```xml
<plugin>
<groupId>org.cqfn.diktat</groupId>
Expand Down Expand Up @@ -101,6 +125,7 @@ Add this plugin to your pom.xml:
</executions>
</plugin>
```
</details>

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`.
Expand All @@ -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`:

<details>
<summary>Add this plugin to your `build.gradle.kts`:</summary>
```kotlin
plugins {
id("org.cqfn.diktat.diktat-gradle-plugin") version "1.0.2"
Expand Down Expand Up @@ -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"
}
```
</details>

You can run diktat checks using task `diktatCheck` and automatically fix errors with tasks `diktatFix`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class DiktatGradlePluginFunctionalTest {
"""${System.lineSeparator()}
diktat {
inputs { include("src/**/*.kt") }
reporterType = "json"
reporter = "json"
output = "test.txt"
}
""".trimIndent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ open class DiktatExtension(
/**
* Type of the reporter to use
*/
var reporterType: String = "plain"
var reporter: String = "plain"

/**
* Type of output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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}")
Expand All @@ -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")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class DiktatJavaExecTaskTest {
) {
inputs { exclude("*") }
diktatConfigFile = project.file("../diktat-analysis.yml")
reporterType = "json"
reporter = "json"
output = "some.txt"
}
}
Expand All @@ -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()
Expand Down
15 changes: 15 additions & 0 deletions diktat-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@
<artifactId>ktlint-reporter-plain</artifactId>
<version>${ktlint.version}</version>
</dependency>
<dependency>
<groupId>com.pinterest.ktlint</groupId>
<artifactId>ktlint-reporter-sarif</artifactId>
<version>${ktlint.version}</version>
</dependency>
<dependency>
<groupId>com.pinterest.ktlint</groupId>
<artifactId>ktlint-reporter-json</artifactId>
<version>${ktlint.version}</version>
</dependency>
<dependency>
<groupId>com.pinterest.ktlint</groupId>
<artifactId>ktlint-reporter-html</artifactId>
<version>${ktlint.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ 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
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
Expand All @@ -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.
Expand Down Expand Up @@ -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")
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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\.\.\."""))
)
Expand Down

0 comments on commit 237cb2c

Please sign in to comment.