diff --git a/README.md b/README.md
index 3d62edf7d2..32bd5febea 100644
--- a/README.md
+++ b/README.md
@@ -168,6 +168,9 @@ $ ktlint --reporter=plain?group_by_file
# print style violations as usual + create report in checkstyle format
$ ktlint --reporter=plain --reporter=checkstyle,output=ktlint-report-in-checkstyle-format.xml
+# check against a baseline file
+$ ktlint --baseline=ktlint-baseline.xml
+
# install git hook to automatically check files for style violations on commit
# Run "ktlint installGitPrePushHook" if you wish to run ktlint on push instead
$ ktlint installGitPreCommitHook
@@ -288,6 +291,8 @@ task ktlint(type: JavaExec, group: "verification") {
args "src/**/*.kt"
// to generate report in checkstyle format prepend following args:
// "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
+ // to add a baseline to check against prepend following args:
+ // "--baseline=ktlint-baseline.xml"
// see https://github.com/pinterest/ktlint#usage for more
}
check.dependsOn ktlint
diff --git a/build.gradle b/build.gradle
index 9dcfb8ea1a..fb7b5b5e93 100644
--- a/build.gradle
+++ b/build.gradle
@@ -42,7 +42,7 @@ task ktlint(type: JavaExec, group: LifecycleBasePlugin.VERIFICATION_GROUP) {
description = "Check Kotlin code style."
classpath = configurations.ktlint
main = 'com.pinterest.ktlint.Main'
- args '*/src/**/*.kt'
+ args '*/src/**/*.kt', '--baseline=ktlint-baseline.xml'
}
allprojects {
diff --git a/ktlint-baseline.xml b/ktlint-baseline.xml
new file mode 100644
index 0000000000..4327ef2957
--- /dev/null
+++ b/ktlint-baseline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ktlint-reporter-baseline/build.gradle b/ktlint-reporter-baseline/build.gradle
new file mode 100644
index 0000000000..437ed77722
--- /dev/null
+++ b/ktlint-reporter-baseline/build.gradle
@@ -0,0 +1,12 @@
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id 'ktlint-publication'
+}
+
+dependencies {
+ implementation project(':ktlint-core')
+ implementation deps.kotlin.stdlib
+
+ testImplementation deps.junit
+ testImplementation deps.assertj
+}
diff --git a/ktlint-reporter-baseline/gradle.properties b/ktlint-reporter-baseline/gradle.properties
new file mode 100644
index 0000000000..3f601372ea
--- /dev/null
+++ b/ktlint-reporter-baseline/gradle.properties
@@ -0,0 +1,4 @@
+GROUP=com.pinterest.ktlint
+POM_NAME=ktlint-reporter-baseline
+POM_ARTIFACT_ID=ktlint-reporter-baseline
+POM_PACKAGING=jar
diff --git a/ktlint-reporter-baseline/src/main/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporter.kt b/ktlint-reporter-baseline/src/main/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporter.kt
new file mode 100644
index 0000000000..a99a191aed
--- /dev/null
+++ b/ktlint-reporter-baseline/src/main/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporter.kt
@@ -0,0 +1,46 @@
+package com.pinterest.ktlint.reporter.baseline
+
+import com.pinterest.ktlint.core.LintError
+import com.pinterest.ktlint.core.Reporter
+import java.io.File
+import java.io.PrintStream
+import java.nio.file.Paths
+import java.util.ArrayList
+import java.util.concurrent.ConcurrentHashMap
+
+class BaselineReporter(val out: PrintStream) : Reporter {
+
+ private val acc = ConcurrentHashMap>()
+
+ override fun onLintError(file: String, err: LintError, corrected: Boolean) {
+ if (!corrected) {
+ acc.getOrPut(file) { ArrayList() }.add(err)
+ }
+ }
+
+ override fun afterAll() {
+ out.println("""""")
+ out.println("""""")
+ for ((file, errList) in acc.entries.sortedBy { it.key }) {
+ val fileName = try {
+ val rootPath = Paths.get("").toAbsolutePath()
+ val filePath = Paths.get(file)
+ rootPath.relativize(filePath).toString().replace(File.separatorChar, '/')
+ } catch (e: IllegalArgumentException) {
+ file
+ }
+ out.println(""" """)
+ for ((line, col, ruleId, _) in errList) {
+ out.println(
+ """ """
+ )
+ }
+ out.println(""" """)
+ }
+ out.println("""""")
+ }
+
+ private fun String.escapeXMLAttrValue() =
+ this.replace("&", "&").replace("\"", """).replace("'", "'")
+ .replace("<", "<").replace(">", ">")
+}
diff --git a/ktlint-reporter-baseline/src/main/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporterProvider.kt b/ktlint-reporter-baseline/src/main/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporterProvider.kt
new file mode 100644
index 0000000000..29394314d9
--- /dev/null
+++ b/ktlint-reporter-baseline/src/main/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporterProvider.kt
@@ -0,0 +1,10 @@
+package com.pinterest.ktlint.reporter.baseline
+
+import com.pinterest.ktlint.core.Reporter
+import com.pinterest.ktlint.core.ReporterProvider
+import java.io.PrintStream
+
+class BaselineReporterProvider : ReporterProvider {
+ override val id: String = "baseline"
+ override fun get(out: PrintStream, opt: Map): Reporter = BaselineReporter(out)
+}
diff --git a/ktlint-reporter-baseline/src/main/resources/META-INF/services/com.pinterest.ktlint.core.ReporterProvider b/ktlint-reporter-baseline/src/main/resources/META-INF/services/com.pinterest.ktlint.core.ReporterProvider
new file mode 100644
index 0000000000..a8e7a91477
--- /dev/null
+++ b/ktlint-reporter-baseline/src/main/resources/META-INF/services/com.pinterest.ktlint.core.ReporterProvider
@@ -0,0 +1 @@
+com.pinterest.ktlint.reporter.baseline.BaselineReporterProvider
diff --git a/ktlint-reporter-baseline/src/test/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporterTest.kt b/ktlint-reporter-baseline/src/test/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporterTest.kt
new file mode 100644
index 0000000000..edcece007c
--- /dev/null
+++ b/ktlint-reporter-baseline/src/test/kotlin/com/pinterest/ktlint/reporter/baseline/BaselineReporterTest.kt
@@ -0,0 +1,75 @@
+package com.pinterest.ktlint.reporter.baseline
+
+import com.pinterest.ktlint.core.LintError
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import java.nio.file.Paths
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class BaselineReporterTest {
+
+ @Test
+ fun testReportGeneration() {
+ val basePath = Paths.get("").toAbsolutePath()
+ val out = ByteArrayOutputStream()
+ val reporter = BaselineReporter(PrintStream(out, true))
+ reporter.onLintError(
+ "$basePath/one-fixed-and-one-not.kt",
+ LintError(
+ 1, 1, "rule-1",
+ "<\"&'>"
+ ),
+ false
+ )
+ reporter.onLintError(
+ "$basePath/one-fixed-and-one-not.kt",
+ LintError(
+ 2, 1, "rule-2",
+ "And if you see my friend"
+ ),
+ true
+ )
+
+ reporter.onLintError(
+ "$basePath/two-not-fixed.kt",
+ LintError(
+ 1, 10, "rule-1",
+ "I thought I would again"
+ ),
+ false
+ )
+ reporter.onLintError(
+ "$basePath/two-not-fixed.kt",
+ LintError(
+ 2, 20, "rule-2",
+ "A single thin straight line"
+ ),
+ false
+ )
+
+ reporter.onLintError(
+ "$basePath/all-corrected.kt",
+ LintError(
+ 1, 1, "rule-1",
+ "I thought we had more time"
+ ),
+ true
+ )
+ reporter.afterAll()
+ assertThat(String(out.toByteArray())).isEqualTo(
+"""
+
+
+
+
+
+
+
+
+
+
+""".trimStart().replace("\n", System.lineSeparator())
+ )
+ }
+}
diff --git a/ktlint/build.gradle b/ktlint/build.gradle
index e87fb87fe8..fb8fce317b 100644
--- a/ktlint/build.gradle
+++ b/ktlint/build.gradle
@@ -25,6 +25,7 @@ publishing.publications.named("maven").configure {
dependencies {
implementation project(':ktlint-core')
+ implementation project(':ktlint-reporter-baseline')
implementation project(':ktlint-reporter-checkstyle')
implementation project(':ktlint-reporter-json')
implementation project(':ktlint-reporter-html')
diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt
index 2f013c2ba1..ccee4eace0 100644
--- a/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt
+++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt
@@ -17,12 +17,15 @@ import com.pinterest.ktlint.internal.GitPrePushHookSubCommand
import com.pinterest.ktlint.internal.JarFiles
import com.pinterest.ktlint.internal.KtlintVersionProvider
import com.pinterest.ktlint.internal.PrintASTSubCommand
+import com.pinterest.ktlint.internal.containsLintError
import com.pinterest.ktlint.internal.fileSequence
import com.pinterest.ktlint.internal.formatFile
import com.pinterest.ktlint.internal.lintFile
+import com.pinterest.ktlint.internal.loadBaseline
import com.pinterest.ktlint.internal.loadRulesets
import com.pinterest.ktlint.internal.location
import com.pinterest.ktlint.internal.printHelpOrVersionUsage
+import com.pinterest.ktlint.internal.relativeRoute
import com.pinterest.ktlint.internal.toFilesURIList
import com.pinterest.ktlint.reporter.plain.internal.Color
import java.io.File
@@ -212,6 +215,12 @@ class KtlintCommandLine {
)
var experimental: Boolean = false
+ @Option(
+ names = ["--baseline"],
+ description = ["Defines a baseline file to check against"]
+ )
+ private var baseline: String = ""
+
@Parameters(hidden = true)
private var patterns = ArrayList()
@@ -224,8 +233,14 @@ class KtlintCommandLine {
val start = System.currentTimeMillis()
+ val baselineResults = loadBaseline(baseline)
val ruleSetProviders = rulesets.loadRulesets(experimental, debug)
- val reporter = loadReporter()
+ var reporter = loadReporter()
+ if (baselineResults.baselineGenerationNeeded) {
+ val baselineReporter = ReporterTemplate("baseline", null, emptyMap(), baseline)
+ val reporterProviderById = loadReporters(emptyList())
+ reporter = Reporter.from(reporter, baselineReporter.toReporter(reporterProviderById))
+ }
val userData = listOfNotNull(
"android" to android.toString(),
if (disabledRules.isNotBlank()) "disabled_rules" to disabledRules else null
@@ -235,7 +250,7 @@ class KtlintCommandLine {
if (stdin) {
lintStdin(ruleSetProviders, userData, reporter)
} else {
- lintFiles(ruleSetProviders, userData, reporter)
+ lintFiles(ruleSetProviders, userData, baselineResults.baselineRules, reporter)
}
reporter.afterAll()
if (debug) {
@@ -253,6 +268,7 @@ class KtlintCommandLine {
private fun lintFiles(
ruleSetProviders: Map,
userData: Map,
+ baseline: Map>?,
reporter: Reporter
) {
patterns.fileSequence()
@@ -263,7 +279,8 @@ class KtlintCommandLine {
file.path,
file.readText(),
ruleSetProviders,
- userData
+ userData,
+ baseline?.get(file.relativeRoute)
)
}
}
@@ -281,7 +298,8 @@ class KtlintCommandLine {
KtLint.STDIN_FILE,
String(System.`in`.readBytes()),
ruleSetProviders,
- userData
+ userData,
+ null
),
reporter
)
@@ -324,7 +342,8 @@ class KtlintCommandLine {
fileName: String,
fileContent: String,
ruleSetProviders: Map,
- userData: Map
+ userData: Map,
+ baselineErrors: List?
): List {
if (debug) {
val fileLocation = if (fileName != KtLint.STDIN_FILE) File(fileName).location(relative) else fileName
@@ -342,8 +361,10 @@ class KtlintCommandLine {
debug
) { err, corrected ->
if (!corrected) {
- result.add(LintErrorWithCorrectionInfo(err, corrected))
- tripped.set(true)
+ if (baselineErrors == null || !baselineErrors.containsLintError(err)) {
+ result.add(LintErrorWithCorrectionInfo(err, corrected))
+ tripped.set(true)
+ }
}
}
} catch (e: Exception) {
@@ -368,8 +389,10 @@ class KtlintCommandLine {
editorConfigPath,
debug
) { err ->
- result.add(LintErrorWithCorrectionInfo(err, false))
- tripped.set(true)
+ if (baselineErrors == null || !baselineErrors.containsLintError(err)) {
+ result.add(LintErrorWithCorrectionInfo(err, false))
+ tripped.set(true)
+ }
}
} catch (e: Exception) {
result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/BaselineUtils.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/BaselineUtils.kt
new file mode 100644
index 0000000000..1bd58f6107
--- /dev/null
+++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/BaselineUtils.kt
@@ -0,0 +1,122 @@
+package com.pinterest.ktlint.internal
+
+import com.pinterest.ktlint.core.LintError
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import java.nio.file.Paths
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.parsers.ParserConfigurationException
+import org.w3c.dom.Element
+import org.xml.sax.SAXException
+
+/**
+ * Loads the baseline file if one is provided.
+ *
+ * @param baselineFilePath the path to the xml baseline file
+ * @return a [CurrentBaseline] with the file details
+ */
+internal fun loadBaseline(baselineFilePath: String): CurrentBaseline {
+ if (baselineFilePath.isBlank()) {
+ return CurrentBaseline(null, false)
+ }
+
+ var baselineRules: Map>? = null
+ var baselineGenerationNeeded = true
+ val baselineFile = Paths.get(baselineFilePath).toFile()
+ if (baselineFile.exists()) {
+ try {
+ baselineRules = parseBaseline(baselineFile.inputStream())
+ baselineGenerationNeeded = false
+ } catch (e: IOException) {
+ System.err.println("Unable to parse baseline file: $baselineFilePath")
+ baselineGenerationNeeded = true
+ } catch (e: ParserConfigurationException) {
+ System.err.println("Unable to parse baseline file: $baselineFilePath")
+ baselineGenerationNeeded = true
+ } catch (e: SAXException) {
+ System.err.println("Unable to parse baseline file: $baselineFilePath")
+ baselineGenerationNeeded = true
+ }
+ }
+
+ // delete the old file if one exists
+ if (baselineGenerationNeeded && baselineFile.exists()) {
+ baselineFile.delete()
+ }
+
+ return CurrentBaseline(baselineRules, baselineGenerationNeeded)
+}
+
+/**
+ * Parses the file to generate a mapping of [LintError]
+ *
+ * @param baselineFile the file containing the current baseline
+ * @return a mapping of file names to a list of all [LintError] in that file
+ */
+internal fun parseBaseline(baselineFile: InputStream): Map> {
+ val baselineRules = HashMap>()
+ val builderFactory = DocumentBuilderFactory.newInstance()
+ val docBuilder = builderFactory.newDocumentBuilder()
+ val doc = docBuilder.parse(baselineFile)
+ val filesList = doc.getElementsByTagName("file")
+ for (i in 0 until filesList.length) {
+ val fileElement = filesList.item(i) as Element
+ val fileName = fileElement.getAttribute("name")
+ val baselineErrors = parseBaselineErrorsByFile(fileElement)
+ baselineRules[fileName] = baselineErrors
+ }
+ return baselineRules
+}
+
+/**
+ * Parses the errors inside each file tag in the xml
+ *
+ * @param element the xml "file" element
+ * @return a list of [LintError] for that file
+ */
+private fun parseBaselineErrorsByFile(element: Element): MutableList {
+ val errors = mutableListOf()
+ val errorsList = element.getElementsByTagName("error")
+ for (i in 0 until errorsList.length) {
+ val errorElement = errorsList.item(i) as Element
+ errors.add(
+ LintError(
+ line = errorElement.getAttribute("line").toInt(),
+ col = errorElement.getAttribute("column").toInt(),
+ ruleId = errorElement.getAttribute("source"),
+ detail = "" // we don't have details in the baseline file
+ )
+ )
+ }
+ return errors
+}
+
+internal class CurrentBaseline(
+ val baselineRules: Map>?,
+ val baselineGenerationNeeded: Boolean
+)
+
+/**
+ * Checks if the list contains the lint error. We cannot use the contains function
+ * as the `checkstyle` reporter formats the details string and hence the comparison
+ * normally fails
+ */
+internal fun List.containsLintError(error: LintError): Boolean {
+ return firstOrNull { lintError ->
+ lintError.col == error.col &&
+ lintError.line == error.line &&
+ lintError.ruleId == error.ruleId
+ } != null
+}
+
+/**
+ * Gets the relative route of the file for baselines
+ * Also adjusts the slashes for uniformity between file systems
+ */
+internal val File.relativeRoute: String
+ get() {
+ val rootPath = Paths.get("").toAbsolutePath()
+ val filePath = this.toPath()
+ return rootPath.relativize(filePath).toString().replace(File.separatorChar, '/')
+ }
diff --git a/ktlint/src/test/kotlin/com/pinterest/ktlint/BaselineTests.kt b/ktlint/src/test/kotlin/com/pinterest/ktlint/BaselineTests.kt
new file mode 100644
index 0000000000..1a7a14b7e1
--- /dev/null
+++ b/ktlint/src/test/kotlin/com/pinterest/ktlint/BaselineTests.kt
@@ -0,0 +1,81 @@
+package com.pinterest.ktlint
+
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import java.security.Permission
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+class BaselineTests {
+
+ @Before
+ fun setup() {
+ System.setSecurityManager(object : SecurityManager() {
+ override fun checkPermission(perm: Permission?) { // allow anything.
+ }
+
+ override fun checkPermission(perm: Permission?, context: Any?) { // allow anything.
+ }
+
+ override fun checkExit(status: Int) {
+ super.checkExit(status)
+ throw ExitException(status)
+ }
+ })
+ }
+
+ @Test
+ fun testNoBaseline() {
+ val stream = ByteArrayOutputStream()
+ val ps = PrintStream(stream)
+ System.setOut(ps)
+
+ try {
+ main(arrayOf("src/test/resources/TestBaselineFile.kt"))
+ } catch (e: ExitException) {
+ // handle System.exit
+ }
+
+ val output = String(stream.toByteArray())
+ assertTrue(output.contains(".*:1:24: Unnecessary block".toRegex()))
+ assertTrue(output.contains(".*:2:1: Unexpected blank line\\(s\\) before \"}\"".toRegex()))
+ }
+
+ @Test
+ fun testBaselineReturnsNoErrors() {
+ val stream = ByteArrayOutputStream()
+ val ps = PrintStream(stream)
+ System.setOut(ps)
+
+ try {
+ main(arrayOf("src/test/resources/TestBaselineFile.kt", "--baseline=src/test/resources/test-baseline.xml"))
+ } catch (e: ExitException) {
+ // handle System.exit
+ }
+
+ val output = String(stream.toByteArray())
+ assertFalse(output.contains(".*:1:24: Unnecessary block".toRegex()))
+ assertFalse(output.contains(".*:2:1: Unexpected blank line\\(s\\) before \"}\"".toRegex()))
+ }
+
+ @Test
+ fun testExtraErrorNotInBaseline() {
+ val stream = ByteArrayOutputStream()
+ val ps = PrintStream(stream)
+ System.setOut(ps)
+
+ try {
+ main(arrayOf("src/test/resources/TestBaselineExtraErrorFile.kt", "--baseline=src/test/resources/test-baseline.xml"))
+ } catch (e: ExitException) {
+ // handle System.exit
+ }
+
+ val output = String(stream.toByteArray())
+ assertFalse(output.contains(".*:1:24: Unnecessary block".toRegex()))
+ assertTrue(output.contains(".*:2:1: Unexpected blank line\\(s\\) before \"}\"".toRegex()))
+ }
+
+ private class ExitException(val status: Int) : SecurityException("Should not exit in tests")
+}
diff --git a/ktlint/src/test/kotlin/com/pinterest/ktlint/internal/BaselineUtilsKtTest.kt b/ktlint/src/test/kotlin/com/pinterest/ktlint/internal/BaselineUtilsKtTest.kt
new file mode 100644
index 0000000000..5e7319972e
--- /dev/null
+++ b/ktlint/src/test/kotlin/com/pinterest/ktlint/internal/BaselineUtilsKtTest.kt
@@ -0,0 +1,44 @@
+package com.pinterest.ktlint.internal
+
+import com.pinterest.ktlint.core.LintError
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class BaselineUtilsKtTest {
+
+ @Test
+ fun testParseBaselineFile() {
+ val filename = "TestBaselineFile.kt"
+ val errorOne = LintError(
+ line = 1,
+ col = 1,
+ ruleId = "final-new-line",
+ detail = ""
+ )
+ val errorTwo = LintError(
+ line = 62,
+ col = 1,
+ ruleId = "no-blank-line-before-rbrace",
+ detail = ""
+ )
+
+ val baseline: InputStream = ByteArrayInputStream(
+ """
+
+
+
+
+ """.toByteArray()
+ )
+
+ val baselineFiles = parseBaseline(baseline)
+
+ assertTrue(baselineFiles.containsKey(filename))
+ assertEquals(2, baselineFiles[filename]?.size)
+ assertTrue(true == baselineFiles[filename]?.containsLintError(errorOne))
+ assertTrue(true == baselineFiles[filename]?.containsLintError(errorTwo))
+ }
+}
diff --git a/ktlint/src/test/resources/TestBaselineExtraErrorFile.kt b/ktlint/src/test/resources/TestBaselineExtraErrorFile.kt
new file mode 100644
index 0000000000..0beb3316d4
--- /dev/null
+++ b/ktlint/src/test/resources/TestBaselineExtraErrorFile.kt
@@ -0,0 +1,3 @@
+class TestBaselineExtraErrorFile {
+
+}
diff --git a/ktlint/src/test/resources/TestBaselineFile.kt b/ktlint/src/test/resources/TestBaselineFile.kt
new file mode 100644
index 0000000000..9f6b6e33b3
--- /dev/null
+++ b/ktlint/src/test/resources/TestBaselineFile.kt
@@ -0,0 +1,3 @@
+class TestBaselineFile {
+
+}
diff --git a/ktlint/src/test/resources/test-baseline.xml b/ktlint/src/test/resources/test-baseline.xml
new file mode 100644
index 0000000000..e4eff23900
--- /dev/null
+++ b/ktlint/src/test/resources/test-baseline.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index d734fac7dd..b10e316ed9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -27,6 +27,7 @@ rootProject.name = 'ktlint'
include ':ktlint'
include ':ktlint-core'
+include ':ktlint-reporter-baseline'
include ':ktlint-reporter-checkstyle'
include ':ktlint-reporter-json'
include ':ktlint-reporter-html'