Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reused 'DiktatProcessor' for inline fix and check #1657

Merged
merged 15 commits into from
Apr 6, 2023
27 changes: 27 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,38 @@ interface DiktatProcessor {
*/
fun fix(file: Path, callback: DiktatCallback): String

/**
* Run `diktat fix` on provided [code] using [callback] for detected errors and returned formatted code.
*
* @param code
* @param isScript
* @param callback
* @return result of `diktat fix`
*/
fun fix(
code: String,
isScript: Boolean,
callback: DiktatCallback,
): String

/**
* Run `diktat check` on provided [file] using [callback] for detected errors.
*
* @param file
* @param callback
*/
fun check(file: Path, callback: DiktatCallback)

/**
* Run `diktat check` on provided [code] using [callback] for detected errors.
*
* @param code
* @param isScript
* @param callback
*/
fun check(
code: String,
isScript: Boolean,
callback: DiktatCallback,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,86 @@ import org.cqfn.diktat.DiktatProcessor
import org.cqfn.diktat.DiktatProcessorFactory
import org.cqfn.diktat.api.DiktatCallback
import org.cqfn.diktat.api.DiktatRuleSet
import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap
import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint
import org.cqfn.diktat.util.isKotlinScript

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.api.EditorConfigOverride

import java.nio.charset.StandardCharsets
import java.nio.file.Path

import kotlin.io.path.absolutePathString
import kotlin.io.path.readText

private typealias KtLintCallback = (LintError, Boolean) -> Unit

/**
* A factory to create [DiktatProcessor] using [DiktatProcessorFactory] and `KtLint` as engine
*/
class DiktatProcessorFactoryImpl : DiktatProcessorFactory {
override fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor = object : DiktatProcessor {
override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(ktLintParams(diktatRuleSet, file, callback.unwrap()))
override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(ktLintParams(diktatRuleSet, file, callback.unwrap()))
override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(file.toKtLintParams(diktatRuleSet, callback))
override fun fix(
code: String,
isScript: Boolean,
callback: DiktatCallback
): String = KtLint.format(code.toKtLintParams(isScript, diktatRuleSet, callback))
override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(file.toKtLintParams(diktatRuleSet, callback))
override fun check(
code: String,
isScript: Boolean,
callback: DiktatCallback
) = KtLint.lint(code.toKtLintParams(isScript, diktatRuleSet, callback))
}

private fun ktLintParams(
diktatRuleSet: DiktatRuleSet,
file: Path,
callback: LintErrorCallback,
): KtLint.ExperimentalParams = KtLint.ExperimentalParams(
fileName = file.absolutePathString(),
text = file.readText(StandardCharsets.UTF_8),
ruleSets = setOf(diktatRuleSet.toKtLint()),
userData = emptyMap(),
cb = callback,
script = file.isKotlinScript(),
editorConfigPath = null,
debug = false, // we do not use it
editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride,
isInvokedFromCli = false
)
companion object {
private fun Path.toKtLintParams(
diktatRuleSet: DiktatRuleSet,
callback: DiktatCallback,
): KtLint.ExperimentalParams = ktLintParams(
fileName = absolutePathString(),
text = readText(StandardCharsets.UTF_8),
isScript = isKotlinScript(),
diktatRuleSet = diktatRuleSet,
callback = callback,
)

private fun String.toKtLintParams(
isScript: Boolean,
diktatRuleSet: DiktatRuleSet,
callback: DiktatCallback,
): KtLint.ExperimentalParams = ktLintParams(
fileName = if (isScript) "test.kts" else "test.kt",
text = this,
isScript = isScript,
diktatRuleSet = diktatRuleSet,
callback = callback,
)

private fun ktLintParams(
fileName: String,
text: String,
isScript: Boolean,
diktatRuleSet: DiktatRuleSet,
callback: DiktatCallback,
): KtLint.ExperimentalParams = KtLint.ExperimentalParams(
fileName = fileName,
text = text,
ruleSets = setOf(diktatRuleSet.toKtLint()),
userData = emptyMap(),
cb = callback.toKtLint(),
script = isScript,
editorConfigPath = null,
debug = false, // we do not use it
editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride,
isInvokedFromCli = false
)

private fun DiktatCallback.toKtLint(): KtLintCallback = { error, isCorrected ->
this(error.wrap(), isCorrected)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,12 @@ package org.cqfn.diktat.ktlint
import org.cqfn.diktat.api.DiktatCallback
import org.cqfn.diktat.api.DiktatRuleSet
import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.unwrap
import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap
import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.KtLint.ExperimentalParams
import com.pinterest.ktlint.core.LintError
import mu.KotlinLogging
import org.intellij.lang.annotations.Language
import java.nio.file.Path
import kotlin.io.path.invariantSeparatorsPathString
import kotlin.io.path.relativeTo

private val log = KotlinLogging.logger { }

val defaultCallback = DiktatCallback { error, _ ->
log.warn { "Received linting error: $error" }
}

typealias LintErrorCallback = (LintError, Boolean) -> Unit

/**
* Makes sure this _rule id_ is qualified with a _rule set id_.
*
Expand All @@ -47,41 +33,24 @@ fun String.qualifiedWithRuleSetId(ruleSetId: String = DIKTAT_RULE_SET_ID): Strin
*/
fun Path.relativePathStringTo(sourceRootDir: Path): String = relativeTo(sourceRootDir).invariantSeparatorsPathString

/**
* @return [DiktatCallback] from KtLint [LintErrorCallback]
*/
fun LintErrorCallback.wrap(): DiktatCallback = DiktatCallback { error, isCorrected ->
this(error.unwrap(), isCorrected)
}

/**
* @return KtLint [LintErrorCallback] from [DiktatCallback] or exception
*/
fun DiktatCallback.unwrap(): LintErrorCallback = { error, isCorrected ->
this(error.wrap(), isCorrected)
}

/**
* Enables ignoring autocorrected errors when in "fix" mode (i.e. when
* [KtLint.format] is invoked).
* [com.pinterest.ktlint.core.KtLint.format] is invoked).
*
* Before version 0.47, _Ktlint_ only reported non-corrected errors in "fix"
* mode.
* Now, this has changed.
*
* @receiver the instance of _Ktlint_ parameters.
* @return the instance with the [callback][ExperimentalParams.cb] modified in
* such a way that it ignores corrected errors.
* @see KtLint.format
* @see ExperimentalParams.cb
* @return the instance [DiktatCallback] that ignores corrected errors.
* @see com.pinterest.ktlint.core.KtLint.format
* @since 1.2.4
*/
private fun ExperimentalParams.ignoreCorrectedErrors(): ExperimentalParams =
copy(cb = { error: LintError, corrected: Boolean ->
if (!corrected) {
cb(error, false)
}
})
private fun DiktatCallback.ignoreCorrectedErrors(): DiktatCallback = DiktatCallback { error, isCorrected ->
if (!isCorrected) {
this@ignoreCorrectedErrors(error, false)
}
}

/**
* @param ruleSetSupplier
Expand All @@ -95,44 +64,46 @@ fun format(
ruleSetSupplier: () -> DiktatRuleSet,
@Language("kotlin") text: String,
fileName: String,
cb: DiktatCallback = defaultCallback
): String {
val ruleSets = listOf(ruleSetSupplier().toKtLint())
return KtLint.format(
ExperimentalParams(
text = text,
ruleSets = ruleSets,
fileName = fileName.removeSuffix("_copy"),
script = fileName.removeSuffix("_copy").endsWith("kts"),
cb = cb.unwrap(),
debug = true,
).ignoreCorrectedErrors()
cb: DiktatCallback,
): String = DiktatProcessorFactoryImpl().invoke(ruleSetSupplier())
.fix(
code = text,
isScript = fileName.removeSuffix("_copy").endsWith("kts"),
callback = cb.ignoreCorrectedErrors(),
)
}

/**
* @param ruleSetSupplier
* @param file
* @param cb callback to be called on unhandled [LintError]s
* @return formatted code
*/
@Suppress("LAMBDA_IS_NOT_LAST_PARAMETER")
fun lint(
ruleSetSupplier: () -> DiktatRuleSet,
file: Path,
cb: DiktatCallback = DiktatCallback.empty
) = DiktatProcessorFactoryImpl().invoke(ruleSetSupplier())
.check(
file = file,
callback = cb.ignoreCorrectedErrors(),
)


/**
* @param ruleSetSupplier
* @param text
* @param fileName
* @param cb callback to be called on unhandled [LintError]s
* @return formatted code
*/
@Suppress("LAMBDA_IS_NOT_LAST_PARAMETER")
fun lint(
ruleSetSupplier: () -> DiktatRuleSet,
@Language("kotlin") text: String,
fileName: String = "test.ks",
cb: DiktatCallback = DiktatCallback.empty
) {
val ruleSets = listOf(ruleSetSupplier().toKtLint())
KtLint.lint(
ExperimentalParams(
text = text,
ruleSets = ruleSets,
fileName = fileName.removeSuffix("_copy"),
script = fileName.removeSuffix("_copy").endsWith("kts"),
cb = cb.unwrap(),
debug = true,
).ignoreCorrectedErrors()
) = DiktatProcessorFactoryImpl().invoke(ruleSetSupplier())
.check(
code = text,
isScript = false,
callback = cb.ignoreCorrectedErrors(),
)
}
Loading