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 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
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