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

Diktat API #1655

Merged
merged 20 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/diktat_snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ jobs:
with:
gradle-version: wrapper
arguments: |
:diktat-api:publishToMavenLocal
:diktat-common:publishToMavenLocal
:diktat-ktlint-engine:publishToMavenLocal
:diktat-rules:publishToMavenLocal
:diktat-runner:diktat-runner-api:publishToMavenLocal
:diktat-runner:diktat-runner-ktlint-engine:publishToMavenLocal
:diktat-gradle-plugin:publishToMavenLocal
:generateLibsForDiktatSnapshot
-x detekt
Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ tasks.create("generateLibsForDiktatSnapshot") {

val dependencies = setOf(
rootProject.project(":diktat-common"),
rootProject.project(":diktat-api"),
rootProject.project(":diktat-ktlint-engine"),
rootProject.project(":diktat-rules"),
rootProject.project(":diktat-runner:diktat-runner-api"),
rootProject.project(":diktat-runner:diktat-runner-ktlint-engine"),
rootProject.project(":diktat-gradle-plugin"),
)
mustRunAfter(dependencies.map { "${it.path}:publishToMavenLocal" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ plugins {
id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration")
}

project.description = "This module builds diktat-runner-api"
project.description = "This module builds diktat-api"

dependencies {
implementation(projects.diktatRules)
implementation(libs.kotlin.compiler.embeddable)
}
26 changes: 26 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatProcessor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.cqfn.diktat

import org.cqfn.diktat.api.DiktatCallback
import java.nio.file.Path

/**
* Processor to run `diktat`
*/
interface DiktatProcessor {
/**
* Run `diktat fix` on provided [file] using [callback] for detected errors and returned formatted file content.
*
* @param file
* @param callback
* @return result of `diktat fix`
*/
fun fix(file: Path, callback: DiktatCallback): String

/**
* Run `diktat check` on provided [file] using [callback] for detected errors.
*
* @param file
* @param callback
*/
fun check(file: Path, callback: DiktatCallback)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.cqfn.diktat

import org.cqfn.diktat.api.DiktatRuleSet

/**
* A factory to create [DiktatProcessor] using [DiktatRuleSet]
*/
@FunctionalInterface
interface DiktatProcessorFactory : Function1<DiktatRuleSet, DiktatProcessor> {
/**
* @param diktatRuleSet
* @return created [DiktatProcessor] using [DiktatRuleSet]
*/
override operator fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor
}
95 changes: 95 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.cqfn.diktat
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

import org.cqfn.diktat.api.DiktatBaseline
import org.cqfn.diktat.api.DiktatBaseline.Companion.skipKnownErrors
import org.cqfn.diktat.api.DiktatProcessorListener
import org.cqfn.diktat.api.DiktatProcessorListener.Companion.countErrorsAsProcessorListener
import org.cqfn.diktat.api.DiktatReporter
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.readText
import kotlin.io.path.writeText

private typealias RunAction = (DiktatProcessor, DiktatProcessorListener) -> Unit

/**
* A runner for diktat on bunch of files using baseline and reporter
*
* @property diktatProcessor
* @property diktatBaseline
* @property diktatBaselineGenerator
* @property diktatReporter
* @property diktatReporterCloser
*/
data class DiktatRunner(
val diktatProcessor: DiktatProcessor,
val diktatBaseline: DiktatBaseline,
private val diktatBaselineGenerator: DiktatProcessorListener,
val diktatReporter: DiktatReporter,
private val diktatReporterCloser: DiktatProcessorListener,
) {
private fun doRun(
args: DiktatRunnerArguments,
runAction: RunAction,
): Int {
val errorCounter = AtomicInteger()
runAction(
diktatProcessor,
DiktatProcessorListener(
args.loggingListener,
diktatReporter.skipKnownErrors(diktatBaseline),
diktatReporterCloser,
diktatBaselineGenerator,
errorCounter.countErrorsAsProcessorListener()
),
)
return errorCounter.get()
}

/**
* Run `diktat fix` for all [files].
*
* @param args
* @param fileUpdateNotifier notifier about updated files
* @return count of detected errors
*/
fun fixAll(
args: DiktatRunnerArguments,
fileUpdateNotifier: (Path) -> Unit,
): Int = doRun(args) { processor, listener ->
listener.beforeAll(args.files)
args.files.forEach { file ->
listener.before(file)
val formattedContent = processor.fix(file) { error, isCorrected ->
listener.onError(file, error, isCorrected)
}
val fileContent = file.readText(Charsets.UTF_8)
if (fileContent != formattedContent) {
fileUpdateNotifier(file)
file.writeText(formattedContent, Charsets.UTF_8)
}
listener.after(file)
}
listener.afterAll()
}

/**
* Run `diktat check` for all [files].
*
* @param args
* @return count of detected errors
*/
fun checkAll(
args: DiktatRunnerArguments,
): Int = doRun(args) { processor, listener ->
listener.beforeAll(args.files)
args.files.forEach { file ->
listener.before(file)
processor.check(file) { error, isCorrected ->
listener.onError(file, error, isCorrected)
}
listener.after(file)
}
listener.afterAll()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.cqfn.diktat

import org.cqfn.diktat.api.DiktatProcessorListener
import java.io.OutputStream
import java.nio.file.Path
import kotlin.io.path.absolutePathString

/**
* Arguments for [DiktatRunner]
*
* @property configFileName a config file to load Diktat's rules
* @property sourceRootDir a common root dir for all provided [files]
* @property files a collection of files which needs to be fixed
* @property baselineFile an optional path to file with baseline
* @property reporterType type of reporter to report the detected errors
* @property reporterOutput output for reporter
* @property loggingListener listener to log diktat runner phases
*/
data class DiktatRunnerArguments(
val configFileName: String,
val sourceRootDir: Path,
val files: Collection<Path>,
val baselineFile: Path?,
val reporterType: String,
val reporterOutput: OutputStream?,
val loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty,
) {
constructor(
configFile: Path,
sourceRootDir: Path,
files: Collection<Path>,
baselineFile: Path?,
reporterType: String,
reporterOutput: OutputStream?,
loggingListener: DiktatProcessorListener,
) : this(
configFile.absolutePathString(),
sourceRootDir,
files,
baselineFile,
reporterType,
reporterOutput,
loggingListener,
)
}
66 changes: 66 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/DiktatRunnerFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.cqfn.diktat

import org.cqfn.diktat.api.DiktatBaseline
import org.cqfn.diktat.api.DiktatBaselineFactory
import org.cqfn.diktat.api.DiktatProcessorListener
import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener
import org.cqfn.diktat.api.DiktatReporter
import org.cqfn.diktat.api.DiktatReporterFactory
import org.cqfn.diktat.api.DiktatRuleSetFactory
import java.io.OutputStream
import java.nio.file.Path

/**
* A factory to create [DiktatRunner]
*/
class DiktatRunnerFactory(
private val diktatRuleSetFactory: DiktatRuleSetFactory,
private val diktatProcessorFactory: DiktatProcessorFactory,
private val diktatBaselineFactory: DiktatBaselineFactory,
private val diktatReporterFactory: DiktatReporterFactory,
) : Function1<DiktatRunnerArguments, DiktatRunner> {
/**
* @param args
* @return an instance of [DiktatRunner] created using [args]
*/
override fun invoke(args: DiktatRunnerArguments): DiktatRunner {
val diktatRuleSet = diktatRuleSetFactory.create(args.configFileName)
val processor = diktatProcessorFactory(diktatRuleSet)
val (baseline, baselineGenerator) = resolveBaseline(args.baselineFile, args.sourceRootDir)
val (reporter, closer) = resolveReporter(args.reporterType, args.reporterOutput, args.sourceRootDir)
return DiktatRunner(
diktatProcessor = processor,
diktatBaseline = baseline,
diktatBaselineGenerator = baselineGenerator,
diktatReporter = reporter,
diktatReporterCloser = closer,
)
}

private fun resolveBaseline(
baselineFile: Path?,
sourceRootDir: Path,
): Pair<DiktatBaseline, DiktatProcessorListener> = baselineFile
?.let { diktatBaselineFactory.tryToLoad(it, sourceRootDir) }
?.let { it to DiktatProcessorListener.empty }
?: run {
val baselineGenerator = baselineFile?.let {
diktatBaselineFactory.generator(it, sourceRootDir)
} ?: DiktatProcessorListener.empty
DiktatBaseline.empty to baselineGenerator
}

private fun resolveReporter(
reporterType: String,
reporterOutput: OutputStream?,
sourceRootDir: Path,
): Pair<DiktatReporter, DiktatProcessorListener> {
val (outputStream, closeListener) = reporterOutput
?.let { it to it.closeAfterAllAsProcessorListener() }
?: run {
System.`out` to DiktatProcessorListener.empty
}
val actualReporter = diktatReporterFactory(reporterType, outputStream, sourceRootDir)
return actualReporter to closeListener
}
}
42 changes: 42 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatBaseline.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.cqfn.diktat.api

import java.nio.file.Path

/**
* A base interface for Baseline
*/
fun interface DiktatBaseline {
/**
* @param file
* @return a set of [DiktatError] found in baseline by [file]
*/
fun errorsByFile(file: Path): Set<DiktatError>

companion object {
/**
* Empty [DiktatBaseline]
*/
val empty: DiktatBaseline = DiktatBaseline { _ -> emptySet() }

/**
* @param baseline
* @return wrapped [DiktatProcessorListener] which skips known errors based on [baseline]
*/
fun DiktatProcessorListener.skipKnownErrors(baseline: DiktatBaseline): DiktatProcessorListener = object : DiktatProcessorListener {
override fun onError(
file: Path,
error: DiktatError,
isCorrected: Boolean
) {
if (!baseline.errorsByFile(file).contains(error)) {
this@skipKnownErrors.onError(file, error, isCorrected)
}
}

override fun beforeAll(files: Collection<Path>) = this@skipKnownErrors.beforeAll(files)
override fun before(file: Path) = this@skipKnownErrors.before(file)
override fun after(file: Path) = this@skipKnownErrors.after(file)
override fun afterAll() = this@skipKnownErrors.afterAll()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.cqfn.diktat.api

import java.nio.file.Path

/**
* A factory to load or generate [DiktatBaseline]
*/
interface DiktatBaselineFactory {
/**
* @param baselineFile
* @param sourceRootDir a dir to detect relative path for processing files
* @return Loaded [DiktatBaseline] from [baselineFile] or null if it gets an error in loading
*/
fun tryToLoad(
baselineFile: Path,
sourceRootDir: Path,
): DiktatBaseline?

/**
* @param baselineFile
* @param sourceRootDir a dir to detect relative path for processing files
* @return [DiktatProcessorListener] which generates baseline in [baselineFile]
*/
fun generator(
baselineFile: Path,
sourceRootDir: Path,
): DiktatProcessorListener
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ fun interface DiktatCallback : Function2<DiktatError, Boolean, Unit> {
* @param isCorrected true if the error fixed by diktat
*/
override fun invoke(error: DiktatError, isCorrected: Boolean)

companion object {
/**
* [DiktatCallback] that does nothing
*/
val empty: DiktatCallback = DiktatCallback { _, _ -> }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.cqfn.diktat.api

/**
* The **file-specific** error emitter, initialized and used in [DiktatRule] implementations.
*
* Since the file is indirectly a part of the state of a `DiktatRule`, the same
* `DiktatRule` instance should **never be re-used** to check more than a single
* file, or confusing effects (incl. race conditions) will occur.
*
* @see DiktatRule
*/
fun interface DiktatErrorEmitter : Function3<Int, String, Boolean, Unit> {
/**
* @param offset
* @param errorMessage
* @param canBeAutoCorrected
*/
override fun invoke(
offset: Int,
errorMessage: String,
canBeAutoCorrected: Boolean
)
}
Loading