Skip to content

Commit

Permalink
Diktat rule configs (#1686)
Browse files Browse the repository at this point in the history
  • Loading branch information
nulls authored Jun 13, 2023
1 parent 4f23c71 commit 4c90d15
Show file tree
Hide file tree
Showing 33 changed files with 508 additions and 478 deletions.
10 changes: 10 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("com.saveourtool.diktat.buildutils.kotlin-jvm-configuration")
id("com.saveourtool.diktat.buildutils.code-quality-convention")
id("com.saveourtool.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 Expand Up @@ -39,3 +41,11 @@ kotlin.sourceSets.getByName("main") {
}
)
}

sequenceOf("diktatFix", "diktatCheck").forEach { diktatTaskName ->
tasks.findByName(diktatTaskName)?.dependsOn(
generateDiktatVersionFile,
tasks.named("compileKotlin"),
tasks.named("processResources"),
)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.saveourtool.diktat

import com.saveourtool.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 +20,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 +41,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 com.saveourtool.diktat.api.DiktatProcessorListener
import com.saveourtool.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener
import com.saveourtool.diktat.api.DiktatReporter
import com.saveourtool.diktat.api.DiktatReporterFactory
import com.saveourtool.diktat.api.DiktatRuleConfigReader
import com.saveourtool.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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.saveourtool.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,
val enabled: Boolean = true,
val configuration: Map<String, String> = emptyMap(),
val ignoreAnnotated: Set<String> = emptySet(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.saveourtool.diktat.api

import java.io.InputStream

/**
* A reader for [DiktatRuleConfig]
*/
fun interface DiktatRuleConfigReader : Function1<InputStream, List<DiktatRuleConfig>> {
/**
* @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 com.saveourtool.diktat.api

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

/**
* A factory which creates a [DiktatRuleSet].
*/
interface DiktatRuleSetFactory : Function0<DiktatRuleSet> {
fun interface DiktatRuleSetFactory : Function1<List<DiktatRuleConfig>, DiktatRuleSet> {
/**
* @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
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.saveourtool.diktat.cli.DiktatProperties
import com.saveourtool.diktat.ktlint.DiktatBaselineFactoryImpl
import com.saveourtool.diktat.ktlint.DiktatProcessorFactoryImpl
import com.saveourtool.diktat.ktlint.DiktatReporterFactoryImpl
import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl
import com.saveourtool.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,42 @@
package com.saveourtool.diktat.common.config.reader

import mu.KotlinLogging
import java.io.IOException
import java.io.InputStream
import kotlin.jvm.Throws

/**
* 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().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]
* @throws IOException
*/
@Throws(IOException::class)
protected abstract fun parse(inputStream: InputStream): T

companion object {
private val log = KotlinLogging.logger {}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@

package com.saveourtool.diktat.common.config.rules

import com.saveourtool.diktat.common.config.reader.JsonResourceConfigReader
import com.saveourtool.diktat.api.DiktatRuleConfig
import com.saveourtool.diktat.common.config.reader.AbstractConfigReader
import com.saveourtool.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 All @@ -38,6 +36,8 @@ const val DIKTAT_RULE_SET_ID = "diktat-ruleset"
const val DIKTAT_ANALYSIS_CONF = "diktat-analysis.yml"
const val DIKTAT_CONF_PROPERTY = "diktat.config.path"

typealias RulesConfig = DiktatRuleConfig

/**
* This interface represents individual inspection in rule set.
*/
Expand All @@ -48,21 +48,6 @@ 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(),
)

/**
* Configuration that allows customizing additional options of particular rules.
* @property config a map of strings with configuration options for a particular rule
Expand All @@ -71,38 +56,17 @@ 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 : AbstractConfigReader<List<RulesConfig>>() {
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 }
}

/**
* 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
*/
override fun getConfigFile(resourceFileName: String): BufferedReader? {
val resourceFile = File(resourceFileName)
return if (resourceFile.exists()) {
log.debug("Using $DIKTAT_ANALYSIS_CONF file from the following path: ${resourceFile.absolutePath}")
File(resourceFileName).bufferedReader()
} else {
log.debug("Using the default $DIKTAT_ANALYSIS_CONF file from the class path")
classLoader.getResourceAsStream(resourceFileName)?.bufferedReader()
}
}
override fun parse(inputStream: InputStream): List<RulesConfig> = yamlSerializer.decodeFromStream(inputStream)

companion object {
internal val log: KLogger = KotlinLogging.logger {}
Expand Down
Loading

0 comments on commit 4c90d15

Please sign in to comment.