Skip to content

Commit

Permalink
Refactor 'CurrentBaseline' to 'Baseline' in public 'api' package (#1569)
Browse files Browse the repository at this point in the history
KtLint does not distinct between old or new baseline. Hence, the term CurrentBaseline
is meaningless. The class was located in the `internal` package but was exposed in
the public API and is used by Diktat. Now the class is moved to the public API
package and documented more properly.
  • Loading branch information
paul-dingemans committed Aug 7, 2022
1 parent d64733b commit 6ed2589
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 173 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ Once above has been implemented, rules no longer have to clean up their internal

The callback function provided as parameter to the format function is now called for all errors regardless whether the error has been autocorrected. Existing consumers of the format function should now explicitly check the `autocorrected` flag in the callback result and handle it appropriately (in most case this will be ignoring the callback results for which `autocorrected` has value `true`).

#### CurrentBaseline

Class `com.pinterest.ktlint.core.internal.CurrentBaseline` has been replaced with `com.pinterest.ktlint.core.api.Baseline`.

Noteworthy changes:
* Field `baselineRules` (nullable) is replaced with `lintErrorsPerFile (non-nullable).
* Field `baselineGenerationNeeded` (boolean) is replaced with `status` (type `Baseline.Status`).

The utility functions provided via `com.pinterest.ktlint.core.internal.CurrentBaseline` are moved to the new class. One new method `List<LintError>.doesNotContain(lintError: LintError)` is added.

### Added

* Add `format` reporter. This reporter prints a one-line-summary of the formatting status per file. ([#621](https://github.com/pinterest/ktlint/issue/621)).
Expand Down
153 changes: 153 additions & 0 deletions ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/api/Baseline.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.pinterest.ktlint.core.api

import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.api.Baseline.Status.INVALID
import com.pinterest.ktlint.core.api.Baseline.Status.NOT_FOUND
import com.pinterest.ktlint.core.api.Baseline.Status.VALID
import com.pinterest.ktlint.core.initKtLintKLogger
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 mu.KotlinLogging
import org.w3c.dom.Element
import org.xml.sax.SAXException

private val logger = KotlinLogging.logger {}.initKtLintKLogger()

/**
* Baseline of lint errors to be ignored in subsequent calls to ktlint.
*/
public class Baseline(
/**
* Lint errors grouped by (relative) file path.
*/
public val lintErrorsPerFile: Map<String, List<LintError>> = emptyMap(),

/**
* Status of the baseline file.
*/
public val status: Status
) {
public enum class Status {
/**
* Baseline file is successfully parsed.
*/
VALID,

/**
* Baseline file does not exist. File needs to be generated by the consumer first.
*/
NOT_FOUND,

/**
* Baseline file is not successfully parsed. File needs to be regenerated by the consumer.
*/
INVALID
}
}

/**
* Loads the [Baseline] from the file located on [path].
*/
public fun loadBaseline(path: String): Baseline {
Paths
.get(path)
.toFile()
.takeIf { it.exists() }
?.let { baselineFile ->
try {
return Baseline(
lintErrorsPerFile = parseBaseline(baselineFile.inputStream()),
status = VALID
)
} catch (e: IOException) {
logger.error { "Unable to parse baseline file: $path" }
} catch (e: ParserConfigurationException) {
logger.error { "Unable to parse baseline file: $path" }
} catch (e: SAXException) {
logger.error { "Unable to parse baseline file: $path" }
}

// Baseline can not be parsed.
try {
baselineFile.delete()
} catch (e: IOException) {
logger.error { "Unable to delete baseline file: $path" }
}
return Baseline(status = INVALID)
}

return Baseline(status = NOT_FOUND)
}

/**
* Parses the [inputStream] of a baseline file and return the lint errors grouped by the relative file names.
*/
internal fun parseBaseline(inputStream: InputStream): Map<String, List<LintError>> {
val lintErrorsPerFile = HashMap<String, List<LintError>>()
val fileNodeList =
DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(inputStream)
.getElementsByTagName("file")
for (i in 0 until fileNodeList.length) {
with(fileNodeList.item(i) as Element) {
lintErrorsPerFile[getAttribute("name")] = parseBaselineErrorsByFile()
}
}
return lintErrorsPerFile
}

/**
* Parses the [LintError]s inside a file element in the xml
*/
private fun Element.parseBaselineErrorsByFile(): List<LintError> {
val errors = mutableListOf<LintError>()
val errorsList = getElementsByTagName("error")
for (i in 0 until errorsList.length) {
errors.add(
with(errorsList.item(i) as Element) {
LintError(
line = getAttribute("line").toInt(),
col = getAttribute("column").toInt(),
ruleId = getAttribute("source"),
detail = "" // Not available in the baseline file
)
}
)
}
return errors
}

/**
* Checks if the list contains the given [LintError]. The [List.contains] function can not be used as [LintError.detail]
* is not available in the baseline file and a normal equality check on the [LintErrpr] fails.
*/
public fun List<LintError>.containsLintError(lintError: LintError): Boolean =
any { it.isSameAs(lintError) }

private fun LintError.isSameAs(lintError: LintError) =
col == lintError.col &&
line == lintError.line &&
ruleId == lintError.ruleId

/**
* Checks if the list does not contain the given [LintError]. The [List.contains] function can not be used as
* [LintError.detail] is not available in the baseline file and a normal equality check on the [LintErrpr] fails.
*/
public fun List<LintError>.doesNotContain(lintError: LintError): Boolean =
none { it.isSameAs(lintError) }

/**
* Gets the relative route of the file for baselines. Also adjusts the slashes for uniformity between file systems
*/
public val File.relativeRoute: String
get() {
val rootPath = Paths.get("").toAbsolutePath()
val filePath = this.toPath()
return rootPath.relativize(filePath).toString().replace(File.separatorChar, '/')
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.pinterest.ktlint.core.internal
package com.pinterest.ktlint.core.api

import com.pinterest.ktlint.core.LintError
import java.io.ByteArrayInputStream
import java.io.InputStream
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class BaselineSupportTest {
class BaselineTest {
@Test
fun testParseBaselineFile() {
val filename = "TestBaselineFile.kt"
Expand All @@ -23,21 +23,22 @@ class BaselineSupportTest {
detail = ""
)

val baseline: InputStream = ByteArrayInputStream(
"""
val baseline: InputStream =
ByteArrayInputStream(
"""
<file name="$filename">
<error line="${errorOne.line}" column="${errorOne.col}" source="${errorOne.ruleId}" />
<error line="${errorTwo.line}" column="${errorTwo.col}" source="${errorTwo.ruleId}" />
</file>
""".toByteArray()
)
""".trimIndent()
.toByteArray()
)

val baselineFiles = parseBaseline(baseline)

assertThat(baselineFiles).containsKey(filename)
val lintErrors = baselineFiles[filename]
assertThat(lintErrors).hasSize(2)
assertThat(lintErrors?.containsLintError(errorOne))
assertThat(lintErrors?.containsLintError(errorTwo))
assertThat(baselineFiles).containsEntry(
filename,
listOf(errorOne, errorTwo)
)
}
}

0 comments on commit 6ed2589

Please sign in to comment.