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 1 commit
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
11 changes: 11 additions & 0 deletions diktat-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("org.cqfn.diktat.buildutils.kotlin-jvm-configuration")
id("org.cqfn.diktat.buildutils.code-quality-convention")
id("org.cqfn.diktat.buildutils.publishing-signing-default-configuration")
}

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

dependencies {
implementation(libs.kotlin.compiler.embeddable)
}
72 changes: 72 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,72 @@
package org.cqfn.diktat

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

/**
* Processor to run `diktat`
*/
abstract class 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`
*/
abstract fun fix(file: Path, callback: DiktatCallback): String

/**
* Run `diktat fix` for all [files] using [listener] during of processing and [formattedCodeHandler] to handle result of `diktat fix`.
*
* @param listener a listener which is called during processing.
* @param files
* @param formattedCodeHandler
*/
fun fixAll(
listener: DiktatProcessorListener = DiktatProcessorListener.empty,
files: Sequence<Path>,
formattedCodeHandler: (Path, String) -> Unit,
) {
listener.beforeAll()
files.forEach { file ->
listener.before(file)
val formattedCode = fix(file) { error, isCorrected ->
listener.onError(file, error, isCorrected)
}
formattedCodeHandler(file, formattedCode)
listener.after(file)
}
listener.afterAll()
}

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

/**
* Run `diktat check` for all [files] using [listener] during of processing.
*
* @param listener a listener which is called during processing.
* @param files
*/
fun checkAll(
listener: DiktatProcessorListener = DiktatProcessorListener.empty,
files: Sequence<Path>,
) {
listener.beforeAll()
files.forEach { file ->
listener.before(file)
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,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
}
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)) {
[email protected](file, error, isCorrected)
}
}

override fun beforeAll() = [email protected]()
override fun before(file: Path) = [email protected](file)
override fun after(file: Path) = [email protected](file)
override fun afterAll() = [email protected]()
}
}
}
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
}
15 changes: 15 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatCallback.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.cqfn.diktat.api

/**
* Callback for diktat process
*/
@FunctionalInterface
fun interface DiktatCallback : Function2<DiktatError, Boolean, Unit> {
/**
* Performs this callback on the given [error] taking into account [isCorrected] flag.
*
* @param error the error found by diktat
* @param isCorrected true if the error fixed by diktat
*/
override fun invoke(error: DiktatError, isCorrected: Boolean)
}
31 changes: 31 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.cqfn.diktat.api

/**
* Error found by `diktat`
*/
interface DiktatError {
/**
* @return line number (one-based)
*/
fun getLine(): Int

/**
* @return column number (one-based)
*/
fun getCol(): Int

/**
* @return rule id
*/
fun getRuleId(): String

/**
* @return error message
*/
fun getDetail(): String

/**
* @return true if the found error can be fixed
*/
fun canBeAutoCorrected(): Boolean
}
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
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.cqfn.diktat.api

import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger

/**
* A listener for [org.cqfn.diktat.DiktatProcessor]
*/
interface DiktatProcessorListener {
/**
* Called once, before [org.cqfn.diktat.DiktatProcessor] starts process a bunch of files.
*/
fun beforeAll(): Unit = Unit

/**
* Called before each file when [org.cqfn.diktat.DiktatProcessor] starts to process it.
*
* @param file
*/
fun before(file: Path): Unit = Unit

/**
* Called on each error when [org.cqfn.diktat.DiktatProcessor] detects such one.
*
* @param file
* @param error
* @param isCorrected
*/
fun onError(
file: Path,
error: DiktatError,
isCorrected: Boolean
)

/**
* Called after each file when [org.cqfn.diktat.DiktatProcessor] finished to process it.
*
* @param file
*/
fun after(file: Path): Unit = Unit

/**
* Called once, after the processing of [org.cqfn.diktat.DiktatProcessor] finished.
*/
fun afterAll(): Unit = Unit

companion object {
/**
* An instance of [DiktatProcessorListener.Empty]
*/
@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR")
val empty = object : Empty() {}

/**
* @param listeners
* @return a single [DiktatProcessorListener] which uses all provided [listeners]
*/
operator fun invoke(vararg listeners: DiktatProcessorListener): DiktatProcessorListener = object : DiktatProcessorListener {
override fun beforeAll() = listeners.forEach(DiktatProcessorListener::beforeAll)
override fun before(file: Path) = listeners.forEach { it.before(file) }
override fun onError(
file: Path,
error: DiktatError,
isCorrected: Boolean
) = listeners.forEach { it.onError(file, error, isCorrected) }
override fun after(file: Path) = listeners.forEach { it.after(file) }
override fun afterAll() = listeners.forEach(DiktatProcessorListener::afterAll)
}

/**
* @return An implementation of [DiktatProcessorListener] which counts [DiktatError]s
*/
fun AtomicInteger.countErrorsAsProcessorListener(): DiktatProcessorListener = object : DiktatProcessorListener {
override fun onError(
file: Path,
error: DiktatError,
isCorrected: Boolean
) {
incrementAndGet()
}
}

/**
* @return An implementation of [DiktatProcessorListener] which closes [AutoCloseable] at the end
*/
fun AutoCloseable.closeAfterAllAsProcessorListener(): DiktatProcessorListener = object : Empty() {
override fun afterAll() {
[email protected]()
}
}

/**
* An empty implementation of [DiktatProcessorListener]
*/
@Suppress("OBJECT_IS_PREFERRED")
abstract class Empty : DiktatProcessorListener {
override fun onError(
file: Path,
error: DiktatError,
isCorrected: Boolean
) = Unit
}
}
}
Loading