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 rule configs #1686

Merged
merged 21 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions diktat-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ 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")
alias(libs.plugins.kotlin.plugin.serialization)
}

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

dependencies {
implementation(libs.kotlin.compiler.embeddable)
implementation(libs.kotlinx.serialization.core)
}

val generateDiktatVersionFile by tasks.registering {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package org.cqfn.diktat

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

/**
* Arguments for [DiktatRunner]
*
* @property configFileName a config file to load Diktat's rules
* @property configInputStream an input stream with config 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
Expand All @@ -19,7 +21,7 @@ import kotlin.io.path.absolutePathString
* @property loggingListener listener to log diktat runner phases, [DiktatProcessorListener.empty] by default
*/
data class DiktatRunnerArguments(
val configFileName: String,
val configInputStream: InputStream,
val sourceRootDir: Path,
val files: Collection<Path>,
val baselineFile: Path?,
Expand All @@ -40,7 +42,7 @@ data class DiktatRunnerArguments(
colorNameInPlain: String? = null,
loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty,
) : this(
configFile.absolutePathString(),
configFile.inputStream(),
sourceRootDir,
files,
baselineFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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.DiktatRuleConfigReader
import org.cqfn.diktat.api.DiktatRuleSetFactory
import java.io.OutputStream
import java.nio.file.Path
Expand All @@ -16,6 +17,7 @@ import java.nio.file.Path
* @property diktatReporterFactory a factory for [DiktatReporter]
*/
class DiktatRunnerFactory(
private val diktatRuleConfigReader: DiktatRuleConfigReader,
private val diktatRuleSetFactory: DiktatRuleSetFactory,
private val diktatProcessorFactory: DiktatProcessorFactory,
private val diktatBaselineFactory: DiktatBaselineFactory,
Expand All @@ -26,7 +28,8 @@ class DiktatRunnerFactory(
* @return an instance of [DiktatRunner] created using [args]
*/
override fun invoke(args: DiktatRunnerArguments): DiktatRunner {
val diktatRuleSet = diktatRuleSetFactory.create(args.configFileName)
val diktatRuleConfigs = diktatRuleConfigReader(args.configInputStream)
val diktatRuleSet = diktatRuleSetFactory(diktatRuleConfigs)
val processor = diktatProcessorFactory(diktatRuleSet)
val (baseline, baselineGenerator) = resolveBaseline(args.baselineFile, args.sourceRootDir)
val (reporter, closer) = resolveReporter(
Expand Down
19 changes: 19 additions & 0 deletions diktat-api/src/main/kotlin/org/cqfn/diktat/api/DiktatRuleConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.cqfn.diktat.api

import kotlinx.serialization.Serializable

/**
* Configuration of individual [DiktatRule]
*
* @property name name of the rule
* @property enabled
* @property configuration a map of strings with configuration options
* @property ignoreAnnotated if a code block is marked with these annotations - it will not be checked by this rule
*/
@Serializable
data class DiktatRuleConfig(
val name: String,
orchestr7 marked this conversation as resolved.
Show resolved Hide resolved
val enabled: Boolean = true,
val configuration: Map<String, String> = emptyMap(),
nulls marked this conversation as resolved.
Show resolved Hide resolved
val ignoreAnnotated: Set<String> = emptySet(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.cqfn.diktat.api

import java.io.InputStream

/**
* A reader for [DiktatRuleConfig]
*/
interface DiktatRuleConfigReader : Function1<InputStream, List<DiktatRuleConfig>> {
nulls marked this conversation as resolved.
Show resolved Hide resolved
/**
* @param inputStream
* @return parsed [DiktatRuleConfig]s
*/
override operator fun invoke(inputStream: InputStream): List<DiktatRuleConfig>
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
package org.cqfn.diktat.api

import java.nio.file.Path
import kotlin.io.path.absolutePathString

/**
* A factory which creates a [DiktatRuleSet].
*/
interface DiktatRuleSetFactory : Function0<DiktatRuleSet> {
interface DiktatRuleSetFactory : Function1<List<DiktatRuleConfig>, DiktatRuleSet> {
nulls marked this conversation as resolved.
Show resolved Hide resolved
/**
* @param rulesConfig all configurations for rules
* @return the default instance of [DiktatRuleSet]
*/
override operator fun invoke(): DiktatRuleSet

/**
* @param configFile a path to file with configuration for diktat (`diktat-analysis.yml`)
* @return created [DiktatRuleSet] using [configFile]
*/
fun create(configFile: String): DiktatRuleSet

/**
* @param configFile a file with configuration for diktat (`diktat-analysis.yml`)
* @return created [DiktatRuleSet] using [configFile]
*/
fun create(configFile: Path): DiktatRuleSet = create(configFile.absolutePathString())
override operator fun invoke(rulesConfig: List<DiktatRuleConfig>): DiktatRuleSet
}
2 changes: 2 additions & 0 deletions diktat-cli/src/main/kotlin/org/cqfn/diktat/DiktatMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.cqfn.diktat.cli.DiktatProperties
import org.cqfn.diktat.ktlint.DiktatBaselineFactoryImpl
import org.cqfn.diktat.ktlint.DiktatProcessorFactoryImpl
import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl
import org.cqfn.diktat.ruleset.rules.DiktatRuleConfigReaderImpl
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl

import mu.KotlinLogging
Expand All @@ -31,6 +32,7 @@ private val loggingListener = object : DiktatProcessorListener {

fun main(args: Array<String>) {
val diktatRunnerFactory = DiktatRunnerFactory(
DiktatRuleConfigReaderImpl(),
DiktatRuleSetFactoryImpl(),
DiktatProcessorFactoryImpl(),
DiktatBaselineFactoryImpl(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import java.io.OutputStream
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.createDirectories
import kotlin.io.path.inputStream
import kotlin.io.path.outputStream
import kotlin.system.exitProcess
import kotlinx.cli.ArgParser
Expand Down Expand Up @@ -69,7 +70,7 @@ data class DiktatProperties(
sourceRootDir: Path,
loggingListener: DiktatProcessorListener,
): DiktatRunnerArguments = DiktatRunnerArguments(
configFileName = config,
configInputStream = Paths.get(config).inputStream(),
sourceRootDir = sourceRootDir,
files = getFiles(sourceRootDir),
baselineFile = null,
Expand Down
1 change: 1 addition & 0 deletions diktat-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
api(libs.kaml)
implementation(libs.apache.commons.cli)
implementation(libs.kotlin.logging)
implementation(projects.diktatApi)
testImplementation(libs.junit.jupiter)
testImplementation(libs.assertj.core)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.cqfn.diktat.common.config.reader

import mu.KotlinLogging
import java.io.IOException
import java.io.InputStream

/**
* This class is used to read input stream in any format that you will specify.
* Usage:
* 1) implement this class with implementing the method:
* a. parse - implement parser for your file format (for example parse it to a proper json)
* 2) Use your new class MyReader(javaClass.classLoader).read(someInputStream)
*
* @param <T> - class name parameter that will be used in calculation of classpath
*/
abstract class AbstractConfigReader<T : Any> {
/**
* @param inputStream - input stream
* @return object of type [T] if resource has been parsed successfully
*/
fun read(inputStream: InputStream): T? = try {
parse(inputStream)
} catch (e: IOException) {
log.error("Cannot read config from input stream due to: ", e)
null
}

/**
* you can specify your own parser, in example for parsing stream as a json
*
* @param inputStream a [InputStream] representing loaded content
* @return resource parsed as type [T]
*/
protected abstract fun parse(inputStream: InputStream): T

companion object {
private val log = KotlinLogging.logger {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@ package org.cqfn.diktat.common.config.reader

import mu.KotlinLogging

import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream

/**
* This class is used to read some resource in any format that you will specify.
* Usage:
* 1) implement this class with implementing two methods:
* a. getConfigFile - to return URI (path) to the file that you want to read
* b. parseResource - implement parser for your file format (for example parse it to a proper json)
* b. parse - implement parser for your file format (for example parse it to a proper json)
* 2) Use your new class MyReader(javaClass.classLoader).readResource("some/path/to/file.format")
*
* @param <T> - class name parameter that will be used in calculation of classpath
* @property classLoader The [ClassLoader] used to load the requested resource.
Fixed Show fixed Hide fixed
* @param T - class name parameter that will be used in calculation of classpath
*/
abstract class JsonResourceConfigReader<T> {
/**
* The [ClassLoader] used to load the requested resource.
*/
abstract val classLoader: ClassLoader

abstract class AbstractResourceConfigReader<T : Any>(
protected val classLoader: ClassLoader
Fixed Show fixed Hide fixed
) : AbstractConfigReader<T>() {
/**
* @param resourceFileName - related path to a file from resources
* @return object of type [T] if resource has been parsed successfully
Expand All @@ -29,7 +27,7 @@ abstract class JsonResourceConfigReader<T> {
val resourceStream = getConfigFile(resourceFileName)
resourceStream?.let {
try {
return parseResource(it)
return parse(it)
} catch (e: IOException) {
log.error("Cannot read config file $resourceFileName due to: ", e)
}
Expand All @@ -42,18 +40,9 @@ abstract class JsonResourceConfigReader<T> {
* you can override this method in case you would like to read a file not simply from resources
*
* @param resourceFileName name of the resource which will be loaded using [classLoader]
* @return [BufferedReader] representing loaded resource
*/
protected open fun getConfigFile(resourceFileName: String): BufferedReader? =
classLoader.getResourceAsStream(resourceFileName)?.bufferedReader()

/**
* you can specify your own parser, in example for parsing stream as a json
*
* @param fileStream a [BufferedReader] representing loaded resource file
* @return resource parsed as type [T]
* @return [InputStream] representing loaded resource
*/
protected abstract fun parseResource(fileStream: BufferedReader): T
protected open fun getConfigFile(resourceFileName: String): InputStream? = classLoader.getResourceAsStream(resourceFileName)

companion object {
private val log = KotlinLogging.logger {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@

package org.cqfn.diktat.common.config.rules

import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader
import org.cqfn.diktat.api.DiktatRuleConfig
import org.cqfn.diktat.common.config.reader.AbstractResourceConfigReader
import org.cqfn.diktat.common.config.rules.RulesConfigReader.Companion.log

import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration
import com.charleskorn.kaml.decodeFromStream
import mu.KLogger
import mu.KotlinLogging

import java.io.BufferedReader
import java.io.File
import java.io.InputStream
import java.util.Locale
import java.util.concurrent.atomic.AtomicInteger

import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString

/**
* Name of common configuration
Expand Down Expand Up @@ -48,20 +49,7 @@ interface Rule {
fun ruleName(): String
}

/**
* Configuration of individual [Rule]
* @property name name of the rule
* @property enabled
* @property configuration a map of strings with configuration options
* @property ignoreAnnotated if a code block is marked with this annotations - it will not be checked by this rule
*/
@Serializable
data class RulesConfig(
val name: String,
val enabled: Boolean = true,
val configuration: Map<String, String> = emptyMap(),
val ignoreAnnotated: Set<String> = emptySet(),
)
typealias RulesConfig = DiktatRuleConfig
Fixed Show fixed Hide fixed

/**
* Configuration that allows customizing additional options of particular rules.
Expand All @@ -73,34 +61,32 @@ open class RuleConfiguration(protected val config: Map<String, String>)
* class returns the list of configurations that we have read from a yml: diktat-analysis.yml
* @property classLoader a [ClassLoader] used to load configuration file
*/
open class RulesConfigReader(override val classLoader: ClassLoader) : JsonResourceConfigReader<List<RulesConfig>>() {
open class RulesConfigReader(classLoader: ClassLoader) : AbstractResourceConfigReader<List<RulesConfig>>(classLoader) {
private val yamlSerializer by lazy { Yaml(configuration = YamlConfiguration(strictMode = true)) }

/**
* Parse resource file into list of [RulesConfig]
*
* @param fileStream a [BufferedReader] representing loaded rules config file
* @param inputStream a [InputStream] representing loaded rules config file
* @return list of [RulesConfig]
*/
override fun parseResource(fileStream: BufferedReader): List<RulesConfig> = fileStream.use { stream ->
yamlSerializer.decodeFromString<List<RulesConfig>>(stream.readLines().joinToString(separator = "\n")).reversed().distinctBy { it.name }
}
override fun parse(inputStream: InputStream): List<RulesConfig> = yamlSerializer.decodeFromStream(inputStream)

/**
* instead of reading the resource as it is done in the interface we will read a file by the absolute path here
* if the path is provided, else will read the hardcoded file 'diktat-analysis.yml' from the package
*
* @param resourceFileName name of the resource which will be loaded using [classLoader]
* @return [BufferedReader] representing loaded resource
* @return [InputStream] representing loaded resource
*/
override fun getConfigFile(resourceFileName: String): BufferedReader? {
override fun getConfigFile(resourceFileName: String): InputStream? {
val resourceFile = File(resourceFileName)
return if (resourceFile.exists()) {
log.debug("Using $DIKTAT_ANALYSIS_CONF file from the following path: ${resourceFile.absolutePath}")
File(resourceFileName).bufferedReader()
File(resourceFileName).inputStream()
} else {
log.debug("Using the default $DIKTAT_ANALYSIS_CONF file from the class path")
classLoader.getResourceAsStream(resourceFileName)?.bufferedReader()
classLoader.getResourceAsStream(resourceFileName)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.cqfn.diktat.ktlint.DiktatReporterFactoryImpl
import org.cqfn.diktat.plugin.gradle.DiktatExtension
import org.cqfn.diktat.plugin.gradle.getOutputFile
import org.cqfn.diktat.plugin.gradle.getReporterType
import org.cqfn.diktat.ruleset.rules.DiktatRuleConfigReaderImpl
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetFactoryImpl

import generated.DIKTAT_VERSION
Expand Down Expand Up @@ -66,6 +67,7 @@ abstract class DiktatTaskBase(
}
private val diktatRunnerFactory by lazy {
DiktatRunnerFactory(
diktatRuleConfigReader = DiktatRuleConfigReaderImpl(),
diktatRuleSetFactory = DiktatRuleSetFactoryImpl(),
diktatProcessorFactory = DiktatProcessorFactoryImpl(),
diktatBaselineFactory = DiktatBaselineFactoryImpl(),
Expand Down
Loading