diff --git a/diktat-api/build.gradle.kts b/diktat-api/build.gradle.kts index 7dd10c71f3..ed59aeca9e 100644 --- a/diktat-api/build.gradle.kts +++ b/diktat-api/build.gradle.kts @@ -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 { @@ -39,3 +41,11 @@ kotlin.sourceSets.getByName("main") { } ) } + +sequenceOf("diktatFix", "diktatCheck").forEach { diktatTaskName -> + tasks.findByName(diktatTaskName)?.dependsOn( + generateDiktatVersionFile, + tasks.named("compileKotlin"), + tasks.named("processResources"), + ) +} diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt index 699e787c69..b6e940fcf7 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerArguments.kt @@ -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 @@ -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, val baselineFile: Path?, @@ -40,7 +41,7 @@ data class DiktatRunnerArguments( colorNameInPlain: String? = null, loggingListener: DiktatProcessorListener = DiktatProcessorListener.empty, ) : this( - configFile.absolutePathString(), + configFile.inputStream(), sourceRootDir, files, baselineFile, diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt index 4cdfc1d570..a5fc58002c 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/DiktatRunnerFactory.kt @@ -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 @@ -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, @@ -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( diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleConfig.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleConfig.kt new file mode 100644 index 0000000000..1de4d90f87 --- /dev/null +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleConfig.kt @@ -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 = emptyMap(), + val ignoreAnnotated: Set = emptySet(), +) diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleConfigReader.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleConfigReader.kt new file mode 100644 index 0000000000..c10c102f51 --- /dev/null +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleConfigReader.kt @@ -0,0 +1,14 @@ +package com.saveourtool.diktat.api + +import java.io.InputStream + +/** + * A reader for [DiktatRuleConfig] + */ +fun interface DiktatRuleConfigReader : Function1> { + /** + * @param inputStream + * @return parsed [DiktatRuleConfig]s + */ + override operator fun invoke(inputStream: InputStream): List +} diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleSetFactory.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleSetFactory.kt index e0ca3e4a7e..51b15dee4e 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleSetFactory.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatRuleSetFactory.kt @@ -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 { +fun interface DiktatRuleSetFactory : Function1, 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): DiktatRuleSet } diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/DiktatMain.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/DiktatMain.kt index 9b0747d3cf..a4642c5dd3 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/DiktatMain.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/DiktatMain.kt @@ -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 @@ -31,6 +32,7 @@ private val loggingListener = object : DiktatProcessorListener { fun main(args: Array) { val diktatRunnerFactory = DiktatRunnerFactory( + DiktatRuleConfigReaderImpl(), DiktatRuleSetFactoryImpl(), DiktatProcessorFactoryImpl(), DiktatBaselineFactoryImpl(), diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt index bd86e2393f..562895e618 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt @@ -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 @@ -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, diff --git a/diktat-common/build.gradle.kts b/diktat-common/build.gradle.kts index 219d3472a1..fbf4fdee4a 100644 --- a/diktat-common/build.gradle.kts +++ b/diktat-common/build.gradle.kts @@ -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) } diff --git a/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/reader/AbstractConfigReader.kt b/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/reader/AbstractConfigReader.kt new file mode 100644 index 0000000000..32b15e18a5 --- /dev/null +++ b/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/reader/AbstractConfigReader.kt @@ -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 { + /** + * @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 {} + } +} diff --git a/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/reader/JsonResourceConfigReader.kt b/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/reader/JsonResourceConfigReader.kt deleted file mode 100644 index 8b442dce87..0000000000 --- a/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/reader/JsonResourceConfigReader.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.saveourtool.diktat.common.config.reader - -import mu.KotlinLogging - -import java.io.BufferedReader -import java.io.IOException - -/** - * 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) - * 2) Use your new class MyReader(javaClass.classLoader).readResource("some/path/to/file.format") - * - * @param - class name parameter that will be used in calculation of classpath - */ -abstract class JsonResourceConfigReader { - /** - * The [ClassLoader] used to load the requested resource. - */ - abstract val classLoader: ClassLoader - - /** - * @param resourceFileName - related path to a file from resources - * @return object of type [T] if resource has been parsed successfully - */ - fun readResource(resourceFileName: String): T? { - val resourceStream = getConfigFile(resourceFileName) - resourceStream?.let { - try { - return parseResource(it) - } catch (e: IOException) { - log.error("Cannot read config file $resourceFileName due to: ", e) - } - } - ?: log.error("Not able to open file $resourceFileName from the resources") - return null - } - - /** - * 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] - */ - protected abstract fun parseResource(fileStream: BufferedReader): T - - companion object { - private val log = KotlinLogging.logger {} - } -} diff --git a/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/rules/RulesConfigReader.kt b/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/rules/RulesConfigReader.kt index db6e7271c1..bb4c2f8a3e 100644 --- a/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/rules/RulesConfigReader.kt +++ b/diktat-common/src/main/kotlin/com/saveourtool/diktat/common/config/rules/RulesConfigReader.kt @@ -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 */ @@ -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. */ @@ -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 = emptyMap(), - val ignoreAnnotated: Set = emptySet(), -) - /** * Configuration that allows customizing additional options of particular rules. * @property config a map of strings with configuration options for a particular rule @@ -71,38 +56,17 @@ open class RuleConfiguration(protected val config: Map) /** * 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>() { +open class RulesConfigReader : AbstractConfigReader>() { 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 = fileStream.use { stream -> - yamlSerializer.decodeFromString>(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 = yamlSerializer.decodeFromStream(inputStream) companion object { internal val log: KLogger = KotlinLogging.logger {} diff --git a/diktat-common/src/test/kotlin/com/saveourtool/diktat/test/ConfigReaderTest.kt b/diktat-common/src/test/kotlin/com/saveourtool/diktat/test/ConfigReaderTest.kt index be37b4403d..1f81e04d44 100644 --- a/diktat-common/src/test/kotlin/com/saveourtool/diktat/test/ConfigReaderTest.kt +++ b/diktat-common/src/test/kotlin/com/saveourtool/diktat/test/ConfigReaderTest.kt @@ -6,12 +6,14 @@ import com.saveourtool.diktat.common.config.rules.RulesConfigReader import com.saveourtool.diktat.common.config.rules.getCommonConfiguration import com.saveourtool.diktat.common.config.rules.kotlinVersion import org.junit.jupiter.api.Test +import java.nio.file.Paths +import kotlin.io.path.inputStream class ConfigReaderTest { @Test fun `testing json reading`() { - val rulesConfigList: List? = RulesConfigReader(javaClass.classLoader) - .readResource("src/test/resources/test-rules-config.yml") + val rulesConfigList: List? = RulesConfigReader() + .read(Paths.get("src/test/resources/test-rules-config.yml").inputStream()) requireNotNull(rulesConfigList) assert(rulesConfigList.any { it.name == "CLASS_NAME_INCORRECT" && it.enabled }) assert(rulesConfigList.find { it.name == "CLASS_NAME_INCORRECT" }?.configuration == emptyMap()) @@ -21,8 +23,8 @@ class ConfigReaderTest { @Test fun `testing kotlin version`() { - val rulesConfigList: List? = RulesConfigReader(javaClass.classLoader) - .readResource("src/test/resources/test-rules-config.yml") + val rulesConfigList: List? = RulesConfigReader() + .read(Paths.get("src/test/resources/test-rules-config.yml").inputStream()) requireNotNull(rulesConfigList) assert(rulesConfigList.getCommonConfiguration().kotlinVersion == kotlinVersion) assert(rulesConfigList.getCommonConfiguration().testAnchors.contains("androidUnitTest")) diff --git a/diktat-dev-ksp/build.gradle.kts b/diktat-dev-ksp/build.gradle.kts index a7582d0519..9b2228cdf6 100644 --- a/diktat-dev-ksp/build.gradle.kts +++ b/diktat-dev-ksp/build.gradle.kts @@ -6,3 +6,10 @@ plugins { dependencies { implementation(libs.kotlin.ksp.api) } + +sequenceOf("diktatFix", "diktatCheck").forEach { diktatTaskName -> + tasks.findByName(diktatTaskName)?.dependsOn( + tasks.named("compileKotlin"), + tasks.named("processResources"), + ) +} diff --git a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt index 16e6a7a716..2eb33a1905 100644 --- a/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/gradle/tasks/DiktatTaskBase.kt @@ -10,6 +10,7 @@ import com.saveourtool.diktat.ktlint.DiktatReporterFactoryImpl import com.saveourtool.diktat.plugin.gradle.DiktatExtension import com.saveourtool.diktat.plugin.gradle.getOutputFile import com.saveourtool.diktat.plugin.gradle.getReporterType +import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import generated.DIKTAT_VERSION @@ -66,6 +67,7 @@ abstract class DiktatTaskBase( } private val diktatRunnerFactory by lazy { DiktatRunnerFactory( + diktatRuleConfigReader = DiktatRuleConfigReaderImpl(), diktatRuleSetFactory = DiktatRuleSetFactoryImpl(), diktatProcessorFactory = DiktatProcessorFactoryImpl(), diktatBaselineFactory = DiktatBaselineFactoryImpl(), diff --git a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt index a483efc615..885fa5c949 100644 --- a/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt +++ b/diktat-gradle-plugin/src/test/kotlin/com/saveourtool/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt @@ -27,6 +27,16 @@ class DiktatJavaExecTaskTest { // mock kotlin sources project.mkdir("src/main/kotlin") project.file("src/main/kotlin/Test.kt").createNewFile() + sequenceOf( + project.file("diktat-analysis.yml"), + project.file("../diktat-analysis.yml") + ).forEach { + it.writeText(""" + # Common configuration + - name: DIKTAT_COMMON + enabled: true + """.trimIndent()) + } } @AfterEach diff --git a/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt b/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt index 9d60b43693..d02dfdf45e 100644 --- a/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt +++ b/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatBaseMojo.kt @@ -6,6 +6,7 @@ import com.saveourtool.diktat.DiktatRunnerFactory 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 org.apache.maven.plugin.AbstractMojo @@ -18,7 +19,11 @@ import org.apache.maven.project.MavenProject import java.io.File import java.io.FileOutputStream import java.io.OutputStream +import java.nio.file.Path +import java.nio.file.Paths import kotlin.io.path.Path +import kotlin.io.path.inputStream +import kotlin.io.path.isRegularFile /** * Base [Mojo] for checking and fixing code using diktat @@ -94,7 +99,7 @@ abstract class DiktatBaseMojo : AbstractMojo() { */ override fun execute() { val configFile = resolveConfig() - if (!File(configFile).exists()) { + if (configFile.isRegularFile()) { throw MojoExecutionException("Configuration file $diktatConfigFile doesn't exist") } log.info("Running diKTat plugin with configuration file $configFile and inputs $inputs" + @@ -103,13 +108,14 @@ abstract class DiktatBaseMojo : AbstractMojo() { val sourceRootDir = mavenProject.basedir.parentFile.toPath() val diktatRunnerFactory = DiktatRunnerFactory( + diktatRuleConfigReader = DiktatRuleConfigReaderImpl(), diktatRuleSetFactory = DiktatRuleSetFactoryImpl(), diktatProcessorFactory = DiktatProcessorFactoryImpl(), diktatBaselineFactory = DiktatBaselineFactoryImpl(), diktatReporterFactory = DiktatReporterFactoryImpl() ) val args = DiktatRunnerArguments( - configFileName = resolveConfig(), + configInputStream = configFile.inputStream(), sourceRootDir = sourceRootDir, files = inputs.map(::Path), baselineFile = baseline?.toPath(), @@ -148,18 +154,18 @@ abstract class DiktatBaseMojo : AbstractMojo() { * If [diktatConfigFile] is absolute, it's path is used. If [diktatConfigFile] is relative, this method looks for it in all maven parent projects. * This way config file can be placed in parent module directory and used in all child modules too. * - * @return path to configuration file as a string. File by this path might not exist. + * @return a configuration file. File by this path might not exist. */ - private fun resolveConfig(): String { - if (File(diktatConfigFile).isAbsolute) { - return diktatConfigFile + private fun resolveConfig(): Path { + val file = Paths.get(diktatConfigFile) + if (file.isAbsolute) { + return file } return generateSequence(mavenProject) { it.parent } - .map { File(it.basedir, diktatConfigFile) } + .map { it.basedir.toPath().resolve(diktatConfigFile) } .run { - firstOrNull { it.exists() } ?: first() + firstOrNull { it.isRegularFile() } ?: first() } - .absolutePath } } diff --git a/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatMojo.kt b/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatMojo.kt index 4770113af6..5f1ce4a428 100644 --- a/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatMojo.kt +++ b/diktat-maven-plugin/src/main/kotlin/com/saveourtool/diktat/plugin/maven/DiktatMojo.kt @@ -6,12 +6,11 @@ package com.saveourtool.diktat.plugin.maven import com.saveourtool.diktat.DiktatRunner import com.saveourtool.diktat.DiktatRunnerArguments -import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetProvider import org.apache.maven.plugins.annotations.Mojo /** - * Main [Mojo] that call [DiktatRuleSetProvider]'s rules on [inputs] files + * Main [Mojo] that call diktat's rules on [inputs] files */ @Mojo(name = "check") @Suppress("unused") @@ -23,7 +22,7 @@ class DiktatCheckMojo : DiktatBaseMojo() { } /** - * Main [Mojo] that call [DiktatRuleSetProvider]'s rules on [inputs] files + * Main [Mojo] that call diktat's rules on [inputs] files * and fixes discovered errors */ @Mojo(name = "fix") diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleConfigReaderImpl.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleConfigReaderImpl.kt new file mode 100644 index 0000000000..fa12a80381 --- /dev/null +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleConfigReaderImpl.kt @@ -0,0 +1,27 @@ +package com.saveourtool.diktat.ruleset.rules + +import com.saveourtool.diktat.api.DiktatRuleConfig +import com.saveourtool.diktat.api.DiktatRuleConfigReader +import com.saveourtool.diktat.common.config.rules.DIKTAT_COMMON +import com.saveourtool.diktat.common.config.rules.RulesConfigReader +import com.saveourtool.diktat.ruleset.constants.Warnings +import org.jetbrains.kotlin.org.jline.utils.Levenshtein +import java.io.InputStream + +/** + * Default implementation for [DiktatRuleConfigReader] + */ +class DiktatRuleConfigReaderImpl : DiktatRuleConfigReader { + private val yamlRuleConfigReader = RulesConfigReader() + + override fun invoke(inputStream: InputStream): List = yamlRuleConfigReader + .read(inputStream) + ?.onEach(::validate) + ?: emptyList() + + private fun validate(config: com.saveourtool.diktat.common.config.rules.RulesConfig) = + require(config.name == DIKTAT_COMMON || config.name in Warnings.names) { + val closestMatch = Warnings.names.minByOrNull { Levenshtein.distance(it, config.name) } + "Warning name <${config.name}> in configuration file is invalid, did you mean <$closestMatch>?" + } +} diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt index ce81d546b0..61d9db3357 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetFactoryImpl.kt @@ -1,13 +1,203 @@ package com.saveourtool.diktat.ruleset.rules +import com.saveourtool.diktat.api.DiktatRuleConfig import com.saveourtool.diktat.api.DiktatRuleSet import com.saveourtool.diktat.api.DiktatRuleSetFactory +import com.saveourtool.diktat.ruleset.rules.chapter1.FileNaming +import com.saveourtool.diktat.ruleset.rules.chapter1.IdentifierNaming +import com.saveourtool.diktat.ruleset.rules.chapter1.PackageNaming +import com.saveourtool.diktat.ruleset.rules.chapter2.comments.CommentsRule +import com.saveourtool.diktat.ruleset.rules.chapter2.comments.HeaderCommentRule +import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.CommentsFormatting +import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.KdocComments +import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.KdocFormatting +import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.KdocMethods +import com.saveourtool.diktat.ruleset.rules.chapter3.AnnotationNewLineRule +import com.saveourtool.diktat.ruleset.rules.chapter3.BlockStructureBraces +import com.saveourtool.diktat.ruleset.rules.chapter3.BooleanExpressionsRule +import com.saveourtool.diktat.ruleset.rules.chapter3.BracesInConditionalsAndLoopsRule +import com.saveourtool.diktat.ruleset.rules.chapter3.ClassLikeStructuresOrderRule +import com.saveourtool.diktat.ruleset.rules.chapter3.CollapseIfStatementsRule +import com.saveourtool.diktat.ruleset.rules.chapter3.ConsecutiveSpacesRule +import com.saveourtool.diktat.ruleset.rules.chapter3.DebugPrintRule +import com.saveourtool.diktat.ruleset.rules.chapter3.EmptyBlock +import com.saveourtool.diktat.ruleset.rules.chapter3.EnumsSeparated +import com.saveourtool.diktat.ruleset.rules.chapter3.LineLength +import com.saveourtool.diktat.ruleset.rules.chapter3.LongNumericalValuesSeparatedRule +import com.saveourtool.diktat.ruleset.rules.chapter3.MagicNumberRule +import com.saveourtool.diktat.ruleset.rules.chapter3.MultipleModifiersSequence +import com.saveourtool.diktat.ruleset.rules.chapter3.NullableTypeRule +import com.saveourtool.diktat.ruleset.rules.chapter3.RangeConventionalRule +import com.saveourtool.diktat.ruleset.rules.chapter3.SingleLineStatementsRule +import com.saveourtool.diktat.ruleset.rules.chapter3.SortRule +import com.saveourtool.diktat.ruleset.rules.chapter3.StringConcatenationRule +import com.saveourtool.diktat.ruleset.rules.chapter3.StringTemplateFormatRule +import com.saveourtool.diktat.ruleset.rules.chapter3.TrailingCommaRule +import com.saveourtool.diktat.ruleset.rules.chapter3.WhenMustHaveElseRule +import com.saveourtool.diktat.ruleset.rules.chapter3.files.BlankLinesRule +import com.saveourtool.diktat.ruleset.rules.chapter3.files.FileSize +import com.saveourtool.diktat.ruleset.rules.chapter3.files.FileStructureRule +import com.saveourtool.diktat.ruleset.rules.chapter3.files.IndentationRule +import com.saveourtool.diktat.ruleset.rules.chapter3.files.NewlinesRule +import com.saveourtool.diktat.ruleset.rules.chapter3.files.TopLevelOrderRule +import com.saveourtool.diktat.ruleset.rules.chapter3.files.WhiteSpaceRule +import com.saveourtool.diktat.ruleset.rules.chapter3.identifiers.LocalVariablesRule +import com.saveourtool.diktat.ruleset.rules.chapter4.ImmutableValNoVarRule +import com.saveourtool.diktat.ruleset.rules.chapter4.NullChecksRule +import com.saveourtool.diktat.ruleset.rules.chapter4.SmartCastRule +import com.saveourtool.diktat.ruleset.rules.chapter4.TypeAliasRule +import com.saveourtool.diktat.ruleset.rules.chapter4.VariableGenericTypeDeclarationRule +import com.saveourtool.diktat.ruleset.rules.chapter4.calculations.AccurateCalculationsRule +import com.saveourtool.diktat.ruleset.rules.chapter5.AsyncAndSyncRule +import com.saveourtool.diktat.ruleset.rules.chapter5.AvoidNestedFunctionsRule +import com.saveourtool.diktat.ruleset.rules.chapter5.CheckInverseMethodRule +import com.saveourtool.diktat.ruleset.rules.chapter5.CustomLabel +import com.saveourtool.diktat.ruleset.rules.chapter5.FunctionArgumentsSize +import com.saveourtool.diktat.ruleset.rules.chapter5.FunctionLength +import com.saveourtool.diktat.ruleset.rules.chapter5.LambdaLengthRule +import com.saveourtool.diktat.ruleset.rules.chapter5.LambdaParameterOrder +import com.saveourtool.diktat.ruleset.rules.chapter5.NestedFunctionBlock +import com.saveourtool.diktat.ruleset.rules.chapter5.OverloadingArgumentsFunction +import com.saveourtool.diktat.ruleset.rules.chapter5.ParameterNameInOuterLambdaRule +import com.saveourtool.diktat.ruleset.rules.chapter6.AvoidEmptyPrimaryConstructor +import com.saveourtool.diktat.ruleset.rules.chapter6.AvoidUtilityClass +import com.saveourtool.diktat.ruleset.rules.chapter6.CustomGetterSetterRule +import com.saveourtool.diktat.ruleset.rules.chapter6.ExtensionFunctionsInFileRule +import com.saveourtool.diktat.ruleset.rules.chapter6.ExtensionFunctionsSameNameRule +import com.saveourtool.diktat.ruleset.rules.chapter6.ImplicitBackingPropertyRule +import com.saveourtool.diktat.ruleset.rules.chapter6.PropertyAccessorFields +import com.saveourtool.diktat.ruleset.rules.chapter6.RunInScript +import com.saveourtool.diktat.ruleset.rules.chapter6.TrivialPropertyAccessors +import com.saveourtool.diktat.ruleset.rules.chapter6.UseLastIndex +import com.saveourtool.diktat.ruleset.rules.chapter6.UselessSupertype +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.AbstractClassesRule +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.CompactInitialization +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.DataClassesRule +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.InlineClassesRule +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.SingleConstructorRule +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.SingleInitRule +import com.saveourtool.diktat.ruleset.rules.chapter6.classes.StatelessClassesRule /** - * A default implementation of [DiktatRuleSetFactory] + * _KtLint_-agnostic factory which creates a [DiktatRuleSet]. + * + * By default, it is expected to have `diktat-analysis.yml` configuration in the root folder where 'ktlint' is run + * otherwise it will use default configuration where some rules are disabled. */ class DiktatRuleSetFactoryImpl : DiktatRuleSetFactory { - override fun invoke(): DiktatRuleSet = DiktatRuleSetProvider().invoke() + /** + * This method is going to be called once for each file (which means if any + * of the rules have state or are not thread-safe - a new [DiktatRuleSet] must + * be created). + * + * For each invocation of [com.pinterest.ktlint.core.KtLintRuleEngine.lint] and [com.pinterest.ktlint.core.KtLintRuleEngine.format] the [DiktatRuleSet] + * is retrieved. + * This results in new instances of each [com.pinterest.ktlint.core.Rule] for each file being + * processed. + * As of that a [com.pinterest.ktlint.core.Rule] does not need to be thread-safe. + * + * However, [com.pinterest.ktlint.core.KtLintRuleEngine.format] requires the [com.pinterest.ktlint.core.Rule] to be executed twice on a + * file in case at least one violation has been autocorrected. + * As the same [Rule] instance is reused for the second execution of the + * [Rule], the state of the [Rule] is shared. + * As of this [Rule] have to clear their internal state. + * + * @return a default [DiktatRuleSet] + */ + @Suppress( + "LongMethod", + "TOO_LONG_FUNCTION", + ) + override fun invoke(rulesConfig: List): DiktatRuleSet { + // Note: the order of rules is important in autocorrect mode. For example, all rules that add new code should be invoked before rules that fix formatting. + // We don't have a way to enforce a specific order, so we should just be careful when adding new rules to this list and, when possible, + // cover new rules in smoke test as well. If a rule needs to be at a specific position in a list, please add comment explaining it (like for NewlinesRule). + val rules = sequenceOf( + // comments & documentation + ::CommentsRule, + ::SingleConstructorRule, // this rule can add properties to a primary constructor, so should be before KdocComments + ::KdocComments, + ::KdocMethods, + ::KdocFormatting, + ::CommentsFormatting, + // naming + ::FileNaming, + ::PackageNaming, + ::IdentifierNaming, + // code structure + ::UselessSupertype, + ::ClassLikeStructuresOrderRule, + ::WhenMustHaveElseRule, + ::BracesInConditionalsAndLoopsRule, + ::EmptyBlock, + ::AvoidEmptyPrimaryConstructor, + ::TopLevelOrderRule, + ::SingleLineStatementsRule, + ::MultipleModifiersSequence, + ::TrivialPropertyAccessors, + ::CustomGetterSetterRule, + ::CompactInitialization, + // other rules + ::UseLastIndex, + ::InlineClassesRule, + ::ExtensionFunctionsInFileRule, + ::CheckInverseMethodRule, + ::StatelessClassesRule, + ::ImplicitBackingPropertyRule, + ::DataClassesRule, + ::LocalVariablesRule, + ::SmartCastRule, + ::AvoidUtilityClass, + ::PropertyAccessorFields, + ::AbstractClassesRule, + ::TrailingCommaRule, + ::SingleInitRule, + ::RangeConventionalRule, + ::DebugPrintRule, + ::CustomLabel, + ::VariableGenericTypeDeclarationRule, + ::LongNumericalValuesSeparatedRule, + ::NestedFunctionBlock, + ::AnnotationNewLineRule, + ::SortRule, + ::EnumsSeparated, + ::StringConcatenationRule, + ::StringTemplateFormatRule, + ::AccurateCalculationsRule, + ::CollapseIfStatementsRule, + ::LineLength, + ::RunInScript, + ::TypeAliasRule, + ::OverloadingArgumentsFunction, + ::FunctionLength, + ::MagicNumberRule, + ::LambdaParameterOrder, + ::FunctionArgumentsSize, + ::BlankLinesRule, + ::FileSize, + ::AsyncAndSyncRule, + ::NullableTypeRule, + ::NullChecksRule, + ::ImmutableValNoVarRule, + ::AvoidNestedFunctionsRule, + ::ExtensionFunctionsSameNameRule, + ::LambdaLengthRule, + ::BooleanExpressionsRule, + ::ParameterNameInOuterLambdaRule, + // formatting: moving blocks, adding line breaks, indentations etc. + ::BlockStructureBraces, + ::ConsecutiveSpacesRule, + ::HeaderCommentRule, + ::FileStructureRule, // this rule should be right before indentation because it should operate on already valid code + ::NewlinesRule, // newlines need to be inserted right before fixing indentation + ::WhiteSpaceRule, // this rule should be after other rules that can cause wrong spacing + ::IndentationRule, // indentation rule should be the last because it fixes formatting after all the changes done by previous rules - override fun create(configFile: String): DiktatRuleSet = DiktatRuleSetProvider(configFile).invoke() + ) + .map { + it(rulesConfig) + } + .toList() + return DiktatRuleSet(rules) + } } diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProvider.kt deleted file mode 100644 index a8db54c156..0000000000 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ /dev/null @@ -1,279 +0,0 @@ -package com.saveourtool.diktat.ruleset.rules - -import com.saveourtool.diktat.api.DiktatRuleSet -import com.saveourtool.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF -import com.saveourtool.diktat.common.config.rules.DIKTAT_COMMON -import com.saveourtool.diktat.common.config.rules.DIKTAT_CONF_PROPERTY -import com.saveourtool.diktat.common.config.rules.DIKTAT_RULE_SET_ID -import com.saveourtool.diktat.common.config.rules.RulesConfig -import com.saveourtool.diktat.common.config.rules.RulesConfigReader -import com.saveourtool.diktat.ruleset.constants.Warnings -import com.saveourtool.diktat.ruleset.rules.chapter1.FileNaming -import com.saveourtool.diktat.ruleset.rules.chapter1.IdentifierNaming -import com.saveourtool.diktat.ruleset.rules.chapter1.PackageNaming -import com.saveourtool.diktat.ruleset.rules.chapter2.comments.CommentsRule -import com.saveourtool.diktat.ruleset.rules.chapter2.comments.HeaderCommentRule -import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.CommentsFormatting -import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.KdocComments -import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.KdocFormatting -import com.saveourtool.diktat.ruleset.rules.chapter2.kdoc.KdocMethods -import com.saveourtool.diktat.ruleset.rules.chapter3.AnnotationNewLineRule -import com.saveourtool.diktat.ruleset.rules.chapter3.BlockStructureBraces -import com.saveourtool.diktat.ruleset.rules.chapter3.BooleanExpressionsRule -import com.saveourtool.diktat.ruleset.rules.chapter3.BracesInConditionalsAndLoopsRule -import com.saveourtool.diktat.ruleset.rules.chapter3.ClassLikeStructuresOrderRule -import com.saveourtool.diktat.ruleset.rules.chapter3.CollapseIfStatementsRule -import com.saveourtool.diktat.ruleset.rules.chapter3.ConsecutiveSpacesRule -import com.saveourtool.diktat.ruleset.rules.chapter3.DebugPrintRule -import com.saveourtool.diktat.ruleset.rules.chapter3.EmptyBlock -import com.saveourtool.diktat.ruleset.rules.chapter3.EnumsSeparated -import com.saveourtool.diktat.ruleset.rules.chapter3.LineLength -import com.saveourtool.diktat.ruleset.rules.chapter3.LongNumericalValuesSeparatedRule -import com.saveourtool.diktat.ruleset.rules.chapter3.MagicNumberRule -import com.saveourtool.diktat.ruleset.rules.chapter3.MultipleModifiersSequence -import com.saveourtool.diktat.ruleset.rules.chapter3.NullableTypeRule -import com.saveourtool.diktat.ruleset.rules.chapter3.RangeConventionalRule -import com.saveourtool.diktat.ruleset.rules.chapter3.SingleLineStatementsRule -import com.saveourtool.diktat.ruleset.rules.chapter3.SortRule -import com.saveourtool.diktat.ruleset.rules.chapter3.StringConcatenationRule -import com.saveourtool.diktat.ruleset.rules.chapter3.StringTemplateFormatRule -import com.saveourtool.diktat.ruleset.rules.chapter3.TrailingCommaRule -import com.saveourtool.diktat.ruleset.rules.chapter3.WhenMustHaveElseRule -import com.saveourtool.diktat.ruleset.rules.chapter3.files.BlankLinesRule -import com.saveourtool.diktat.ruleset.rules.chapter3.files.FileSize -import com.saveourtool.diktat.ruleset.rules.chapter3.files.FileStructureRule -import com.saveourtool.diktat.ruleset.rules.chapter3.files.IndentationRule -import com.saveourtool.diktat.ruleset.rules.chapter3.files.NewlinesRule -import com.saveourtool.diktat.ruleset.rules.chapter3.files.TopLevelOrderRule -import com.saveourtool.diktat.ruleset.rules.chapter3.files.WhiteSpaceRule -import com.saveourtool.diktat.ruleset.rules.chapter3.identifiers.LocalVariablesRule -import com.saveourtool.diktat.ruleset.rules.chapter4.ImmutableValNoVarRule -import com.saveourtool.diktat.ruleset.rules.chapter4.NullChecksRule -import com.saveourtool.diktat.ruleset.rules.chapter4.SmartCastRule -import com.saveourtool.diktat.ruleset.rules.chapter4.TypeAliasRule -import com.saveourtool.diktat.ruleset.rules.chapter4.VariableGenericTypeDeclarationRule -import com.saveourtool.diktat.ruleset.rules.chapter4.calculations.AccurateCalculationsRule -import com.saveourtool.diktat.ruleset.rules.chapter5.AsyncAndSyncRule -import com.saveourtool.diktat.ruleset.rules.chapter5.AvoidNestedFunctionsRule -import com.saveourtool.diktat.ruleset.rules.chapter5.CheckInverseMethodRule -import com.saveourtool.diktat.ruleset.rules.chapter5.CustomLabel -import com.saveourtool.diktat.ruleset.rules.chapter5.FunctionArgumentsSize -import com.saveourtool.diktat.ruleset.rules.chapter5.FunctionLength -import com.saveourtool.diktat.ruleset.rules.chapter5.LambdaLengthRule -import com.saveourtool.diktat.ruleset.rules.chapter5.LambdaParameterOrder -import com.saveourtool.diktat.ruleset.rules.chapter5.NestedFunctionBlock -import com.saveourtool.diktat.ruleset.rules.chapter5.OverloadingArgumentsFunction -import com.saveourtool.diktat.ruleset.rules.chapter5.ParameterNameInOuterLambdaRule -import com.saveourtool.diktat.ruleset.rules.chapter6.AvoidEmptyPrimaryConstructor -import com.saveourtool.diktat.ruleset.rules.chapter6.AvoidUtilityClass -import com.saveourtool.diktat.ruleset.rules.chapter6.CustomGetterSetterRule -import com.saveourtool.diktat.ruleset.rules.chapter6.ExtensionFunctionsInFileRule -import com.saveourtool.diktat.ruleset.rules.chapter6.ExtensionFunctionsSameNameRule -import com.saveourtool.diktat.ruleset.rules.chapter6.ImplicitBackingPropertyRule -import com.saveourtool.diktat.ruleset.rules.chapter6.PropertyAccessorFields -import com.saveourtool.diktat.ruleset.rules.chapter6.RunInScript -import com.saveourtool.diktat.ruleset.rules.chapter6.TrivialPropertyAccessors -import com.saveourtool.diktat.ruleset.rules.chapter6.UseLastIndex -import com.saveourtool.diktat.ruleset.rules.chapter6.UselessSupertype -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.AbstractClassesRule -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.CompactInitialization -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.DataClassesRule -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.InlineClassesRule -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.SingleConstructorRule -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.SingleInitRule -import com.saveourtool.diktat.ruleset.rules.chapter6.classes.StatelessClassesRule - -import mu.KotlinLogging -import org.jetbrains.kotlin.org.jline.utils.Levenshtein - -import java.io.File - -/** - * _KtLint_-agnostic factory which creates a [DiktatRuleSet]. - * - * By default, it is expected to have `diktat-analysis.yml` configuration in the root folder where 'ktlint' is run - * otherwise it will use default configuration where some rules are disabled. - * - * @param diktatConfigFile the configuration file where all configurations for - * inspections and rules are stored. - */ -class DiktatRuleSetProvider(private val diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) { - private val possibleConfigs: Sequence = sequence { - yield(resolveDefaultConfig()) - yield(resolveConfigFileFromJarLocation()) - yield(resolveConfigFileFromSystemProperty()) - } - private val configRules: List by lazy { - log.debug("Will run $DIKTAT_RULE_SET_ID with $diktatConfigFile" + - " (it can be placed to the run directory or the default file from resources will be used)") - val configPath = possibleConfigs - .firstOrNull { it != null && File(it).exists() } - val resultedDiktatConfigFile = configPath - ?: run { - val possibleConfigsList = possibleConfigs.toList() - log.warn( - "Configuration file not found in directory where diktat is run (${possibleConfigsList[0]}) " + - "or in the directory where diktat.jar is stored (${possibleConfigsList[1]}) " + - "or in system property (${possibleConfigsList[2]}), " + - "the default file included in jar will be used. " + - "Some configuration options will be disabled or substituted with defaults. " + - "Custom configuration file should be placed in diktat working directory if run from CLI " + - "or provided as configuration options in plugins." - ) - diktatConfigFile - } - - RulesConfigReader(javaClass.classLoader) - .readResource(resultedDiktatConfigFile) - ?.onEach(::validate) - ?: emptyList() - } - - /** - * This method is going to be called once for each file (which means if any - * of the rules have state or are not thread-safe - a new [DiktatRuleSet] must - * be created). - * - * For each invocation of [com.pinterest.ktlint.core.KtLintRuleEngine.lint] and [com.pinterest.ktlint.core.KtLintRuleEngine.format] the [DiktatRuleSet] - * is retrieved. - * This results in new instances of each [com.pinterest.ktlint.core.Rule] for each file being - * processed. - * As of that a [com.pinterest.ktlint.core.Rule] does not need to be thread-safe. - * - * However, [com.pinterest.ktlint.core.KtLintRuleEngine.format] requires the [com.pinterest.ktlint.core.Rule] to be executed twice on a - * file in case at least one violation has been autocorrected. - * As the same [Rule] instance is reused for the second execution of the - * [Rule], the state of the [Rule] is shared. - * As of this [Rule] have to clear their internal state. - * - * @return a default [DiktatRuleSet] - */ - @Suppress( - "LongMethod", - "TOO_LONG_FUNCTION", - ) - operator fun invoke(): DiktatRuleSet { - // Note: the order of rules is important in autocorrect mode. For example, all rules that add new code should be invoked before rules that fix formatting. - // We don't have a way to enforce a specific order, so we should just be careful when adding new rules to this list and, when possible, - // cover new rules in smoke test as well. If a rule needs to be at a specific position in a list, please add comment explaining it (like for NewlinesRule). - val rules = sequenceOf( - // comments & documentation - ::CommentsRule, - ::SingleConstructorRule, // this rule can add properties to a primary constructor, so should be before KdocComments - ::KdocComments, - ::KdocMethods, - ::KdocFormatting, - ::CommentsFormatting, - // naming - ::FileNaming, - ::PackageNaming, - ::IdentifierNaming, - // code structure - ::UselessSupertype, - ::ClassLikeStructuresOrderRule, - ::WhenMustHaveElseRule, - ::BracesInConditionalsAndLoopsRule, - ::EmptyBlock, - ::AvoidEmptyPrimaryConstructor, - ::TopLevelOrderRule, - ::SingleLineStatementsRule, - ::MultipleModifiersSequence, - ::TrivialPropertyAccessors, - ::CustomGetterSetterRule, - ::CompactInitialization, - // other rules - ::UseLastIndex, - ::InlineClassesRule, - ::ExtensionFunctionsInFileRule, - ::CheckInverseMethodRule, - ::StatelessClassesRule, - ::ImplicitBackingPropertyRule, - ::DataClassesRule, - ::LocalVariablesRule, - ::SmartCastRule, - ::AvoidUtilityClass, - ::PropertyAccessorFields, - ::AbstractClassesRule, - ::TrailingCommaRule, - ::SingleInitRule, - ::RangeConventionalRule, - ::DebugPrintRule, - ::CustomLabel, - ::VariableGenericTypeDeclarationRule, - ::LongNumericalValuesSeparatedRule, - ::NestedFunctionBlock, - ::AnnotationNewLineRule, - ::SortRule, - ::EnumsSeparated, - ::StringConcatenationRule, - ::StringTemplateFormatRule, - ::AccurateCalculationsRule, - ::CollapseIfStatementsRule, - ::LineLength, - ::RunInScript, - ::TypeAliasRule, - ::OverloadingArgumentsFunction, - ::FunctionLength, - ::MagicNumberRule, - ::LambdaParameterOrder, - ::FunctionArgumentsSize, - ::BlankLinesRule, - ::FileSize, - ::AsyncAndSyncRule, - ::NullableTypeRule, - ::NullChecksRule, - ::ImmutableValNoVarRule, - ::AvoidNestedFunctionsRule, - ::ExtensionFunctionsSameNameRule, - ::LambdaLengthRule, - ::BooleanExpressionsRule, - ::ParameterNameInOuterLambdaRule, - // formatting: moving blocks, adding line breaks, indentations etc. - ::BlockStructureBraces, - ::ConsecutiveSpacesRule, - ::HeaderCommentRule, - ::FileStructureRule, // this rule should be right before indentation because it should operate on already valid code - ::NewlinesRule, // newlines need to be inserted right before fixing indentation - ::WhiteSpaceRule, // this rule should be after other rules that can cause wrong spacing - ::IndentationRule, // indentation rule should be the last because it fixes formatting after all the changes done by previous rules - - ) - .map { - it(configRules) - } - .toList() - return DiktatRuleSet(rules) - } - - private fun validate(config: RulesConfig) = - require(config.name == DIKTAT_COMMON || config.name in Warnings.names) { - val closestMatch = Warnings.names.minByOrNull { Levenshtein.distance(it, config.name) } - "Warning name <${config.name}> in configuration file is invalid, did you mean <$closestMatch>?" - } - - private fun resolveDefaultConfig() = diktatConfigFile - - private fun resolveConfigFileFromJarLocation(): String { - // for some aggregators of static analyzers we need to provide configuration for cli - // in this case diktat would take the configuration from the directory where jar file is stored - val ruleSetProviderPath = - javaClass - .protectionDomain - .codeSource - .location - .toURI() - - val configPathWithFileName = File(ruleSetProviderPath).absolutePath - - val indexOfName = configPathWithFileName.lastIndexOf(File.separator) - val configPath = if (indexOfName > -1) configPathWithFileName.substring(0, indexOfName) else configPathWithFileName - - return "$configPath${File.separator}$diktatConfigFile" - } - - private fun resolveConfigFileFromSystemProperty(): String? = System.getProperty(DIKTAT_CONF_PROPERTY) - - companion object { - private val log = KotlinLogging.logger {} - } -} diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt index b49b366b8e..f55242671c 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt @@ -28,9 +28,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiCommentImpl import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.kdoc.lexer.KDocTokens -import org.jetbrains.kotlin.kdoc.lexer.KDocTokens.END import org.jetbrains.kotlin.kdoc.lexer.KDocTokens.KDOC -import org.jetbrains.kotlin.kdoc.lexer.KDocTokens.TEXT import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.BLOCK_COMMENT diff --git a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt index 792594e280..02d0dcd292 100644 --- a/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt +++ b/diktat-rules/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt @@ -37,7 +37,6 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.kdoc.lexer.KDocTokens import org.jetbrains.kotlin.kdoc.lexer.KDocTokens.KDOC -import org.jetbrains.kotlin.kdoc.lexer.KDocTokens.MARKDOWN_LINK import org.jetbrains.kotlin.lexer.KtTokens.BLOCK_COMMENT import org.jetbrains.kotlin.lexer.KtTokens.EOL_COMMENT import org.jetbrains.kotlin.lexer.KtTokens.WHITE_SPACE diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/RulesConfigValidationTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/RulesConfigValidationTest.kt index f17d7f0dcd..a3c12b2edd 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/RulesConfigValidationTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/RulesConfigValidationTest.kt @@ -1,6 +1,7 @@ package com.saveourtool.diktat.ruleset.smoke -import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetProvider +import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl +import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import com.saveourtool.diktat.test.framework.util.deleteIfExistsSilently import com.charleskorn.kaml.InvalidPropertyValueException @@ -39,7 +40,7 @@ class RulesConfigValidationTest { """.trimMargin() ) val exception = assertThrows { - DiktatRuleSetProvider(file.absolutePath).invoke() + diktatRuleSetFactory(diktatRuleConfigReader(file.inputStream())) } Assertions.assertEquals("Warning name in configuration file is invalid, did you mean ?", exception.message) } @@ -54,7 +55,7 @@ class RulesConfigValidationTest { """.trimMargin() ) assertThrows { - DiktatRuleSetProvider(file.absolutePath).invoke() + diktatRuleSetFactory(diktatRuleConfigReader(file.inputStream())) } } @@ -70,6 +71,11 @@ class RulesConfigValidationTest { | isIncludeHeader: Fslse """.trimMargin() ) - DiktatRuleSetProvider(file.absolutePath).invoke() + diktatRuleSetFactory(diktatRuleConfigReader(file.inputStream())) + } + + companion object { + private val diktatRuleSetFactory = DiktatRuleSetFactoryImpl() + private val diktatRuleConfigReader = DiktatRuleConfigReaderImpl() } } diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/utils/RulesConfigYamlTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/utils/RulesConfigYamlTest.kt index f30153a503..01463d3194 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/utils/RulesConfigYamlTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/ruleset/utils/RulesConfigYamlTest.kt @@ -12,6 +12,9 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.io.File +import java.nio.file.Paths +import kotlin.io.path.exists +import kotlin.io.path.inputStream import kotlinx.serialization.encodeToString @@ -107,9 +110,12 @@ class RulesConfigYamlTest { } } - private fun readAllRulesFromConfig(nameConfig: String) = - RulesConfigReader(javaClass.classLoader) - .readResource(nameConfig) ?: emptyList() + private fun readAllRulesFromConfig(nameConfig: String) = run { + Paths.get(nameConfig).takeIf { it.exists() }?.inputStream() + ?: javaClass.classLoader.getResourceAsStream(nameConfig) + } + ?.let { RulesConfigReader().read(it) } + ?: emptyList() private fun readAllRulesFromCode() = Warnings.values() diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/DiktatRuleSetProviderTest.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/DiktatRuleSetFactoryImplTest.kt similarity index 84% rename from diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/DiktatRuleSetProviderTest.kt rename to diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/DiktatRuleSetFactoryImplTest.kt index 3ec513853d..201e98c8d9 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/DiktatRuleSetProviderTest.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/DiktatRuleSetFactoryImplTest.kt @@ -8,7 +8,7 @@ import com.saveourtool.diktat.api.DiktatRuleSet import com.saveourtool.diktat.common.config.rules.RulesConfig import com.saveourtool.diktat.common.config.rules.RulesConfigReader import com.saveourtool.diktat.ruleset.rules.DiktatRule -import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetProvider +import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import com.saveourtool.diktat.test.framework.util.filterContentMatches import org.assertj.core.api.Assertions.assertThat @@ -21,11 +21,11 @@ import kotlin.io.path.isRegularFile import kotlin.io.path.nameWithoutExtension import kotlin.io.path.walk -class DiktatRuleSetProviderTest { +class DiktatRuleSetFactoryImplTest { @OptIn(ExperimentalPathApi::class) @Suppress("UnsafeCallOnNullableType") @Test - fun `check DiktatRuleSetProviderTest contain all rules`() { + fun `check DiktatRuleSetFactoryImpl contain all rules`() { val path = "${System.getProperty("user.dir")}/src/main/kotlin/com/saveourtool/diktat/ruleset/rules" val fileNames = Path(path) .walk() @@ -34,8 +34,8 @@ class DiktatRuleSetProviderTest { .map(Path::nameWithoutExtension) .filterNot { it in ignoredFileNames } .toList() - val ruleNames = DiktatRuleSetProvider() - .invoke() + val ruleNames = DiktatRuleSetFactoryImpl() + .invoke(emptyList()) .rules .asSequence() .map { it::class.simpleName } @@ -63,8 +63,8 @@ class DiktatRuleSetProviderTest { ruleSupplier: (rulesConfigList: List) -> DiktatRule, rulesConfigList: List?, ): DiktatRuleSet = run { - rulesConfigList ?: RulesConfigReader(Companion::class.java.classLoader) - .readResource("diktat-analysis.yml") + rulesConfigList ?: Companion::class.java.classLoader.getResourceAsStream("diktat-analysis.yml") + ?.let { RulesConfigReader().read(it) } .orEmpty() } .let(ruleSupplier) diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/FixTestBase.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/FixTestBase.kt index 2be4e95e13..7178bdacb4 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/FixTestBase.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/FixTestBase.kt @@ -7,7 +7,7 @@ import com.saveourtool.diktat.ruleset.rules.DiktatRule import com.saveourtool.diktat.test.framework.processing.FileComparisonResult import com.saveourtool.diktat.test.framework.processing.ResourceReader import com.saveourtool.diktat.test.framework.processing.TestComparatorUnit -import com.saveourtool.diktat.util.DiktatRuleSetProviderTest.Companion.diktatRuleSetForTest +import com.saveourtool.diktat.util.DiktatRuleSetFactoryImplTest.Companion.diktatRuleSetForTest import mu.KotlinLogging import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions diff --git a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/LintTestBase.kt b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/LintTestBase.kt index 43df41d6cd..22ea6c5977 100644 --- a/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/LintTestBase.kt +++ b/diktat-rules/src/test/kotlin/com/saveourtool/diktat/util/LintTestBase.kt @@ -4,7 +4,7 @@ import com.saveourtool.diktat.api.DiktatCallback import com.saveourtool.diktat.common.config.rules.RulesConfig import com.saveourtool.diktat.ktlint.lint import com.saveourtool.diktat.ruleset.rules.DiktatRule -import com.saveourtool.diktat.util.DiktatRuleSetProviderTest.Companion.diktatRuleSetForTest +import com.saveourtool.diktat.util.DiktatRuleSetFactoryImplTest.Companion.diktatRuleSetForTest import com.saveourtool.diktat.api.DiktatError import org.assertj.core.api.Assertions.assertThat import org.intellij.lang.annotations.Language diff --git a/diktat-ruleset/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProviderV3Spi.kt b/diktat-ruleset/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProviderV3Spi.kt index d1134dca45..d7ef359452 100644 --- a/diktat-ruleset/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProviderV3Spi.kt +++ b/diktat-ruleset/src/main/kotlin/com/saveourtool/diktat/ruleset/rules/DiktatRuleSetProviderV3Spi.kt @@ -1,5 +1,7 @@ package com.saveourtool.diktat.ruleset.rules +import com.saveourtool.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF +import com.saveourtool.diktat.common.config.rules.DIKTAT_CONF_PROPERTY import com.saveourtool.diktat.common.config.rules.DIKTAT_RULE_SET_ID import com.saveourtool.diktat.ktlint.KtLintRuleWrapper.Companion.toKtLint import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 @@ -8,6 +10,8 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.RuleSetId import mu.KotlinLogging import org.slf4j.Logger +import java.io.File +import java.io.InputStream /** * [RuleSetProviderV3] that provides diKTat ruleset. @@ -22,10 +26,81 @@ import org.slf4j.Logger class DiktatRuleSetProviderV3Spi : RuleSetProviderV3( id = RuleSetId(DIKTAT_RULE_SET_ID), ) { + private val diktatRuleConfigReader = DiktatRuleConfigReaderImpl() + private val diktatRuleSetFactory = DiktatRuleSetFactoryImpl() + init { // Need to init KtLint logger to set log level from CLI KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).initKtLintKLogger() } - override fun getRuleProviders(): Set = DiktatRuleSetProvider().invoke().toKtLint() + override fun getRuleProviders(): Set = diktatRuleSetFactory(diktatRuleConfigReader(readConfigFile())) + .toKtLint() + + private fun readConfigFile(): InputStream { + val resourceFileName = resolveConfigFile() + val resourceFile = File(resourceFileName) + return if (resourceFile.exists()) { + log.debug { "Using $DIKTAT_ANALYSIS_CONF file from the following path: ${resourceFile.absolutePath}" } + resourceFile.inputStream() + } else { + log.debug { "Using the default $DIKTAT_ANALYSIS_CONF file from the class path" } + javaClass.classLoader.getResourceAsStream(resourceFileName) ?: run { + log.error { "Not able to open file $resourceFileName from the resources" } + object : InputStream() { + override fun read(): Int = -1 + } + } + } + } + + private fun resolveConfigFile(): String { + val possibleConfigs: Sequence = sequence { + yield(DIKTAT_ANALYSIS_CONF) + yield(resolveConfigFileFromJarLocation()) + yield(resolveConfigFileFromSystemProperty()) + } + + log.debug("Will run $DIKTAT_RULE_SET_ID with $DIKTAT_ANALYSIS_CONF" + + " (it can be placed to the run directory or the default file from resources will be used)") + val configPath = possibleConfigs + .firstOrNull { it != null && File(it).exists() } + return configPath + ?: run { + val possibleConfigsList = possibleConfigs.toList() + log.warn( + "Configuration file not found in directory where diktat is run (${possibleConfigsList[0]}) " + + "or in the directory where diktat.jar is stored (${possibleConfigsList[1]}) " + + "or in system property (${possibleConfigsList[2]}), " + + "the default file included in jar will be used. " + + "Some configuration options will be disabled or substituted with defaults. " + + "Custom configuration file should be placed in diktat working directory if run from CLI " + + "or provided as configuration options in plugins." + ) + DIKTAT_ANALYSIS_CONF + } + } + + private fun resolveConfigFileFromJarLocation(): String { + // for some aggregators of static analyzers we need to provide configuration for cli + // in this case diktat would take the configuration from the directory where jar file is stored + val ruleSetProviderPath = javaClass + .protectionDomain + .codeSource + .location + .toURI() + + val configPathWithFileName = File(ruleSetProviderPath).absolutePath + + val indexOfName = configPathWithFileName.lastIndexOf(File.separator) + val configPath = if (indexOfName > -1) configPathWithFileName.substring(0, indexOfName) else configPathWithFileName + + return "$configPath${File.separator}$DIKTAT_ANALYSIS_CONF" + } + + private fun resolveConfigFileFromSystemProperty(): String? = System.getProperty(DIKTAT_CONF_PROPERTY) + + companion object { + private val log = KotlinLogging.logger {} + } } diff --git a/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTest.kt index ff575f36c7..01874baabc 100644 --- a/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -2,15 +2,16 @@ package com.saveourtool.diktat.ruleset.smoke import com.saveourtool.diktat.api.DiktatError import com.saveourtool.diktat.ktlint.format -import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetProvider +import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl +import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl import com.saveourtool.diktat.test.framework.processing.TestComparatorUnit import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import java.nio.file.Path -import kotlin.io.path.absolutePathString +import kotlin.io.path.inputStream /** - * Test for [DiktatRuleSetProvider] in autocorrect mode as a whole. All rules are applied to a file. + * Test for [DiktatRuleSetFactoryImpl] in autocorrect mode as a whole. All rules are applied to a file. * Note: ktlint uses initial text from a file to calculate line and column from offset. Because of that line/col of unfixed errors * may change after some changes to text or other rules. */ @@ -42,7 +43,11 @@ class DiktatSmokeTest : DiktatSmokeTestBase() { resourceFilePath = RESOURCE_FILE_PATH, function = { testFile -> format( - ruleSetSupplier = { DiktatRuleSetProvider(config.absolutePathString()).invoke() }, + ruleSetSupplier = { + val diktatRuleConfigReader = DiktatRuleConfigReaderImpl() + val diktatRuleSetFactory = DiktatRuleSetFactoryImpl() + diktatRuleSetFactory(diktatRuleConfigReader(config.inputStream())) + }, file = testFile, cb = { lintError, _ -> unfixedLintErrors.add(lintError) }, ) diff --git a/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTestBase.kt b/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTestBase.kt index 61da709e7c..b366f5b29a 100644 --- a/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTestBase.kt +++ b/diktat-ruleset/src/test/kotlin/com/saveourtool/diktat/ruleset/smoke/DiktatSmokeTestBase.kt @@ -46,10 +46,12 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import java.io.File import java.nio.file.Path +import java.nio.file.Paths import java.time.LocalDate import java.util.concurrent.TimeUnit.SECONDS import kotlin.io.path.createTempFile +import kotlin.io.path.inputStream import kotlin.io.path.writeText import kotlinx.serialization.builtins.ListSerializer @@ -68,7 +70,7 @@ abstract class DiktatSmokeTestBase { */ @Suppress("UnsafeCallOnNullableType") private fun prepareOverriddenRulesConfig(rulesToDisable: List = emptyList(), rulesToOverride: RuleToConfig = emptyMap()): Path { - val rulesConfig = RulesConfigReader(javaClass.classLoader).readResource(DEFAULT_CONFIG_PATH)!! + val rulesConfig = RulesConfigReader().read(Paths.get(DEFAULT_CONFIG_PATH).inputStream())!! .toMutableList() .also { rulesConfig -> rulesToDisable.forEach { warning -> diff --git a/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestArgumentsReader.kt b/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestArgumentsReader.kt index 426f7081d4..b5c2dc3a47 100644 --- a/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestArgumentsReader.kt +++ b/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestArgumentsReader.kt @@ -1,7 +1,7 @@ package com.saveourtool.diktat.test.framework.config import com.saveourtool.diktat.common.cli.CliArgument -import com.saveourtool.diktat.common.config.reader.JsonResourceConfigReader +import com.saveourtool.diktat.common.config.reader.AbstractConfigReader import mu.KotlinLogging import org.apache.commons.cli.CommandLine @@ -11,27 +11,27 @@ import org.apache.commons.cli.HelpFormatter import org.apache.commons.cli.Options import org.apache.commons.cli.ParseException -import java.io.BufferedReader import java.io.IOException -import java.util.stream.Collectors +import java.io.InputStream import kotlin.system.exitProcess -import kotlinx.serialization.decodeFromString +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream /** * Class that gives access to properties of a test * + * @param classLoader [ClassLoader] which is used to load properties file * @property args CLI arguments * @property properties properties from properties file - * @property classLoader [ClassLoader] which is used to load properties file */ class TestArgumentsReader( private val args: Array, val properties: TestFrameworkProperties, - override val classLoader: ClassLoader -) : JsonResourceConfigReader?>() { - private val cliArguments: List? = readResource(properties.testFrameworkArgsRelativePath) + classLoader: ClassLoader +) : AbstractConfigReader>() { + private val cliArguments: List? = classLoader.getResourceAsStream(properties.testFrameworkArgsRelativePath)?.let { read(it) } private val cmd: CommandLine by lazy { parseArguments() } /** @@ -82,14 +82,12 @@ class TestArgumentsReader( /** * Parse JSON to a list of [CliArgument]s * - * @param fileStream a [BufferedReader] representing input JSON + * @param inputStream a [InputStream] representing input JSON * @return list of [CliArgument]s */ + @OptIn(ExperimentalSerializationApi::class) @Throws(IOException::class) - override fun parseResource(fileStream: BufferedReader): List { - val jsonValue = fileStream.lines().collect(Collectors.joining()) - return Json.decodeFromString(jsonValue) - } + override fun parse(inputStream: InputStream): List = Json.decodeFromStream(inputStream) companion object { private val log = KotlinLogging.logger {} diff --git a/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestConfigReader.kt b/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestConfigReader.kt index 741c6c0a25..d8351fd451 100644 --- a/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestConfigReader.kt +++ b/diktat-test-framework/src/main/kotlin/com/saveourtool/diktat/test/framework/config/TestConfigReader.kt @@ -1,33 +1,28 @@ package com.saveourtool.diktat.test.framework.config -import com.saveourtool.diktat.common.config.reader.JsonResourceConfigReader +import com.saveourtool.diktat.common.config.reader.AbstractConfigReader -import java.io.BufferedReader import java.io.IOException -import java.util.stream.Collectors +import java.io.InputStream import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream /** - * A [JsonResourceConfigReader] to read tests configuration as [TestConfig] - * @property classLoader a [ClassLoader] to load configutation file + * A [AbstractConfigReader] to read tests configuration as [TestConfig] */ -class TestConfigReader(configFilePath: String, override val classLoader: ClassLoader) : JsonResourceConfigReader() { +class TestConfigReader(configFilePath: String, classLoader: ClassLoader) : AbstractConfigReader() { /** * The [TestConfig] which is read from */ - val config: TestConfig? = readResource(configFilePath) + val config: TestConfig? = classLoader.getResourceAsStream(configFilePath)?.let { read(it) } /** - * @param fileStream input stream of data from config file + * @param inputStream input stream of data from config file * @return [TestConfig] read from file */ @OptIn(ExperimentalSerializationApi::class) @Throws(IOException::class) - override fun parseResource(fileStream: BufferedReader): TestConfig { - val jsonValue: String = fileStream.lines().collect(Collectors.joining()) - return Json.decodeFromString(jsonValue) - } + override fun parse(inputStream: InputStream): TestConfig = Json.decodeFromStream(inputStream) }