diff --git a/.editorconfig b/.editorconfig index 5f30b45a1d..d8cc0b33b1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,6 @@ +# https://editorconfig.org +root = true + [*] charset = utf-8 indent_size = 4 @@ -99,3 +102,9 @@ ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false + +# disable ktlint rules +ktlint_standard = disabled +ktlint_experimental = disabled +ktlint_test = disabled +ktlint_custom = disabled diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5042ca3eb8..ae65e84100 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -113,7 +113,7 @@ jobs: if: matrix.type == 'ktlint' run: | filename=$(ls -1 diktat-*.jar | head -n1) - echo DIKTAT_RUN="java -jar ktlint -R \"$filename\" --disabled_rules=standard,experimental,test,custom" >> $GITHUB_ENV + echo DIKTAT_RUN="java -jar ktlint -R \"$filename\"" >> $GITHUB_ENV shell: bash - name: Download diktat cli jar diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt index de461702f4..d3fb2cedc1 100644 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatBaselineFactoryImpl.kt @@ -6,7 +6,8 @@ import org.cqfn.diktat.api.DiktatProcessorListener import org.cqfn.diktat.api.DiktatProcessorListener.Companion.closeAfterAllAsProcessorListener import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap import org.cqfn.diktat.ktlint.DiktatReporterImpl.Companion.wrap -import com.pinterest.ktlint.core.internal.loadBaseline +import com.pinterest.ktlint.core.api.Baseline +import com.pinterest.ktlint.core.api.loadBaseline import com.pinterest.ktlint.reporter.baseline.BaselineReporter import java.io.PrintStream import java.nio.file.Path @@ -16,17 +17,15 @@ import kotlin.io.path.outputStream /** * A factory to create or generate [DiktatBaseline] using `KtLint` */ -@Suppress("DEPRECATION") class DiktatBaselineFactoryImpl : DiktatBaselineFactory { override fun tryToLoad( baselineFile: Path, sourceRootDir: Path, ): DiktatBaseline? = loadBaseline(baselineFile.absolutePathString()) - .takeUnless { it.baselineGenerationNeeded } + .takeIf { it.status == Baseline.Status.VALID } ?.let { ktLintBaseline -> DiktatBaseline { file -> - ktLintBaseline.baselineRules - ?.get(file.relativePathStringTo(sourceRootDir)) + ktLintBaseline.lintErrorsPerFile[file.relativePathStringTo(sourceRootDir)] .orEmpty() .map { it.wrap() } .toSet() diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt index c616077c6e..8a3dc4e53f 100644 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatProcessorFactoryImpl.kt @@ -5,85 +5,56 @@ import org.cqfn.diktat.DiktatProcessorFactory import org.cqfn.diktat.api.DiktatCallback import org.cqfn.diktat.api.DiktatRuleSet import org.cqfn.diktat.ktlint.DiktatErrorImpl.Companion.wrap -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import org.cqfn.diktat.util.isKotlinScript - -import com.pinterest.ktlint.core.KtLint +import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.toKtLint +import com.pinterest.ktlint.core.Code +import com.pinterest.ktlint.core.KtLintRuleEngine import com.pinterest.ktlint.core.LintError -import com.pinterest.ktlint.core.api.EditorConfigOverride - -import java.nio.charset.StandardCharsets import java.nio.file.Path -import kotlin.io.path.absolutePathString -import kotlin.io.path.readText - -private typealias KtLintCallback = (LintError, Boolean) -> Unit +private typealias FormatCallback = (LintError, Boolean) -> Unit +private typealias LintCallback = (LintError) -> Unit /** * A factory to create [DiktatProcessor] using [DiktatProcessorFactory] and `KtLint` as engine */ class DiktatProcessorFactoryImpl : DiktatProcessorFactory { - override fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor = object : DiktatProcessor { - override fun fix(file: Path, callback: DiktatCallback): String = KtLint.format(file.toKtLintParams(diktatRuleSet, callback)) - override fun fix( - code: String, - isScript: Boolean, - callback: DiktatCallback - ): String = KtLint.format(code.toKtLintParams(isScript, diktatRuleSet, callback)) - override fun check(file: Path, callback: DiktatCallback) = KtLint.lint(file.toKtLintParams(diktatRuleSet, callback)) - override fun check( - code: String, - isScript: Boolean, - callback: DiktatCallback - ) = KtLint.lint(code.toKtLintParams(isScript, diktatRuleSet, callback)) + override fun invoke(diktatRuleSet: DiktatRuleSet): DiktatProcessor { + val ktLintRuleEngine = diktatRuleSet.toKtLintEngine() + return object : DiktatProcessor { + override fun fix( + file: Path, + callback: DiktatCallback, + ): String = ktLintRuleEngine.format(file.toKtLint(), callback.toKtLintForFormat()) + override fun fix( + code: String, + isScript: Boolean, + callback: DiktatCallback + ): String = ktLintRuleEngine.format(code.toKtLint(isScript), callback.toKtLintForFormat()) + override fun check( + file: Path, + callback: DiktatCallback, + ) = ktLintRuleEngine.lint(file.toKtLint(), callback.toKtLintForLint()) + override fun check( + code: String, + isScript: Boolean, + callback: DiktatCallback + ) = ktLintRuleEngine.lint(code.toKtLint(isScript), callback.toKtLintForLint()) + } } companion object { - private fun Path.toKtLintParams( - diktatRuleSet: DiktatRuleSet, - callback: DiktatCallback, - ): KtLint.ExperimentalParams = ktLintParams( - fileName = absolutePathString(), - text = readText(StandardCharsets.UTF_8).replace("\r\n", "\n").replace("\r", "\n"), - isScript = isKotlinScript(), - diktatRuleSet = diktatRuleSet, - callback = callback, - ) + private fun DiktatRuleSet.toKtLintEngine(): KtLintRuleEngine = KtLintRuleEngine(ruleProviders = toKtLint()) - private fun String.toKtLintParams( - isScript: Boolean, - diktatRuleSet: DiktatRuleSet, - callback: DiktatCallback, - ): KtLint.ExperimentalParams = ktLintParams( - fileName = if (isScript) "test.kts" else "test.kt", - text = this, - isScript = isScript, - diktatRuleSet = diktatRuleSet, - callback = callback, - ) + private fun Path.toKtLint(): Code = Code.CodeFile(this.toFile()) - private fun ktLintParams( - fileName: String, - text: String, - isScript: Boolean, - diktatRuleSet: DiktatRuleSet, - callback: DiktatCallback, - ): KtLint.ExperimentalParams = KtLint.ExperimentalParams( - fileName = fileName, - text = text, - ruleSets = setOf(diktatRuleSet.toKtLint()), - userData = emptyMap(), - cb = callback.toKtLint(), - script = isScript, - editorConfigPath = null, - debug = false, // we do not use it - editorConfigOverride = EditorConfigOverride.emptyEditorConfigOverride, - isInvokedFromCli = false - ) + private fun String.toKtLint(isScript: Boolean): Code = Code.CodeSnippet(this, isScript) - private fun DiktatCallback.toKtLint(): KtLintCallback = { error, isCorrected -> + private fun DiktatCallback.toKtLintForFormat(): FormatCallback = { error, isCorrected -> this(error.wrap(), isCorrected) } + + private fun DiktatCallback.toKtLintForLint(): LintCallback = { error -> + this(error.wrap(), false) + } } } diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt index 96425526d1..a47f2cec8b 100644 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -7,8 +7,8 @@ import com.pinterest.ktlint.core.ReporterProvider import com.pinterest.ktlint.reporter.checkstyle.CheckStyleReporterProvider import com.pinterest.ktlint.reporter.html.HtmlReporterProvider import com.pinterest.ktlint.reporter.json.JsonReporterProvider +import com.pinterest.ktlint.reporter.plain.Color import com.pinterest.ktlint.reporter.plain.PlainReporterProvider -import com.pinterest.ktlint.reporter.plain.internal.Color import com.pinterest.ktlint.reporter.sarif.SarifReporterProvider import java.io.OutputStream import java.io.PrintStream diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt deleted file mode 100644 index 937a7b8457..0000000000 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapper.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqfn.diktat.ktlint - -import org.cqfn.diktat.api.DiktatRule -import org.cqfn.diktat.api.DiktatRuleSet -import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID -import com.pinterest.ktlint.core.Rule -import com.pinterest.ktlint.core.RuleSet - -/** - * This is a wrapper around __KtLint__'s [RuleSet] which adjusts visitorModifiers for all rules to keep order with prevRule - * Added as a workaround after introducing a new logic for sorting KtLint Rules: https://github.com/pinterest/ktlint/issues/1478 - * - * @param diktatRuleSet the rules which belong to the current [DiktatRuleSet]. - */ -class KtLintRuleSetWrapper private constructor( - diktatRuleSet: DiktatRuleSet, -) : RuleSet(DIKTAT_RULE_SET_ID, rules = wrapRules(diktatRuleSet.rules)) { - companion object { - /** - * @return __KtLint__'s [RuleSet] created from [DiktatRuleSet] - */ - fun DiktatRuleSet.toKtLint(): RuleSet = KtLintRuleSetWrapper(this) - - private fun wrapRules(rules: List): Array { - if (rules.isEmpty()) { - return emptyArray() - } - return rules.asSequence() - .runningFold(null as KtLintRuleWrapper?) { prevRule, diktatRule -> - KtLintRuleWrapper(diktatRule, prevRule) - } - .filterNotNull() - .toList() - .toTypedArray() - } - } -} diff --git a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt index b4dbca3505..e7435e13d8 100644 --- a/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt +++ b/diktat-ktlint-engine/src/main/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapper.kt @@ -1,8 +1,10 @@ package org.cqfn.diktat.ktlint import org.cqfn.diktat.api.DiktatRule +import org.cqfn.diktat.api.DiktatRuleSet import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.RuleProvider import org.jetbrains.kotlin.com.intellij.lang.ASTNode private typealias EmitType = (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit @@ -18,17 +20,26 @@ class KtLintRuleWrapper( id = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID), visitorModifiers = createVisitorModifiers(rule, prevRule), ) { - @Deprecated( - "Marked for deletion in ktlint 0.48.0", - replaceWith = ReplaceWith("beforeVisitChildNodes(node, autoCorrect, emit)"), - ) - override fun visit( + override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, emit: EmitType, ) = rule.invoke(node, autoCorrect, emit) companion object { + private fun Sequence.wrapRules(): Sequence = runningFold(null as KtLintRuleWrapper?) { prevRule, diktatRule -> + KtLintRuleWrapper(diktatRule, prevRule) + }.filterNotNull() + + /** + * @return [Set] of __KtLint__'s [RuleProvider]s created from [DiktatRuleSet] + */ + fun DiktatRuleSet.toKtLint(): Set = rules + .asSequence() + .wrapRules() + .map { it.asProvider() } + .toSet() + private fun createVisitorModifiers( rule: DiktatRule, prevRule: KtLintRuleWrapper?, @@ -51,5 +62,10 @@ class KtLintRuleWrapper( * @return a rule to which a logic is delegated */ internal fun Rule.unwrap(): DiktatRule = (this as? KtLintRuleWrapper)?.rule ?: error("Provided rule ${javaClass.simpleName} is not wrapped by diktat") + + /** + * @return wraps [Rule] to [RuleProvider] + */ + internal fun Rule.asProvider(): RuleProvider = RuleProvider { this } } } diff --git a/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt b/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapperTest.kt similarity index 75% rename from diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt rename to diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapperTest.kt index 1f3098db45..343e64d25a 100644 --- a/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleSetWrapperTest.kt +++ b/diktat-ktlint-engine/src/test/kotlin/org/cqfn/diktat/ktlint/KtLintRuleWrapperTest.kt @@ -1,19 +1,21 @@ package org.cqfn.diktat.ktlint +import com.pinterest.ktlint.core.Code +import com.pinterest.ktlint.core.KtLintRuleEngine import org.cqfn.diktat.api.DiktatErrorEmitter import org.cqfn.diktat.api.DiktatRule import org.cqfn.diktat.api.DiktatRuleSet -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint +import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.toKtLint import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.unwrap -import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.RuleProvider import org.assertj.core.api.Assertions.assertThat import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -class KtLintRuleSetWrapperTest { +class KtLintRuleWrapperTest { @Test fun `check KtLintRuleSetWrapper with duplicate`() { val rule = mockRule("rule") @@ -27,12 +29,12 @@ class KtLintRuleSetWrapperTest { val rule1 = mockRule(id = "rule-first") val rule2 = mockRule(id = "rule-second") - val orderedRuleSet = DiktatRuleSet(listOf(rule1, rule2)).toKtLint() + val orderedRuleProviders = DiktatRuleSet(listOf(rule1, rule2)).toKtLint() - val orderedRuleSetIterator = orderedRuleSet.rules.iterator() - val orderedRule1 = orderedRuleSetIterator.next() - val orderedRule2 = orderedRuleSetIterator.next() - Assertions.assertFalse(orderedRuleSetIterator.hasNext(), "Extra elements after ordering") + val orderedRuleProviderIterator = orderedRuleProviders.iterator() + val orderedRule1 = orderedRuleProviderIterator.next().createNewRuleInstance() + val orderedRule2 = orderedRuleProviderIterator.next().createNewRuleInstance() + Assertions.assertFalse(orderedRuleProviderIterator.hasNext(), "Extra elements after ordering") Assertions.assertEquals(rule1, orderedRule1.unwrap(), "First rule is modified") @@ -74,18 +76,18 @@ class KtLintRuleSetWrapperTest { /* * Make sure OrderedRuleSet preserves the order. */ - val ruleSet = DiktatRuleSet(rules).toKtLint() - assertThat(ruleSet.rules.map(Rule::id)).containsExactlyElementsOf(rules.map(DiktatRule::id).map { it.qualifiedWithRuleSetId() }) + val ruleProviders = DiktatRuleSet(rules).toKtLint() + assertThat(ruleProviders.map(RuleProvider::createNewRuleInstance).map(Rule::id)) + .containsExactlyElementsOf(rules.map(DiktatRule::id).map { it.qualifiedWithRuleSetId() }) @Language("kotlin") val code = "fun foo() { }" - KtLint.lint( - KtLint.ExperimentalParams( - fileName = "TestFileName.kt", - text = code, - ruleSets = listOf(ruleSet), - cb = { _, _ -> }, + KtLintRuleEngine( + ruleProviders = ruleProviders + ).lint( + code = Code.CodeSnippet( + content = code ) ) @@ -123,20 +125,13 @@ class KtLintRuleSetWrapperTest { * | * V * C(File) -> C(Node) -> C(Leaf) - * - * val expectedRuleInvocationOrder = rules.asSequence() - * .map(Rule::id) - * .flatMap { ruleId -> - * generateSequence { ruleId }.take(astNodeCount) - * } - * .toList() */ - val expectedRuleInvocationOrder = generateSequence { - rules.map(DiktatRule::id) - } - .take(astNodeCount) - .flatten() - .toList() + val expectedRuleInvocationOrder = rules.asSequence() + .map(DiktatRule::id) + .flatMap { ruleId -> + generateSequence { ruleId }.take(astNodeCount) + } + .toList() assertThat(actualRuleInvocationOrder) .containsExactlyElementsOf(expectedRuleInvocationOrder) diff --git a/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojoTest.kt b/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojoTest.kt index b5aaab4e6d..48d6c8f680 100644 --- a/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojoTest.kt +++ b/diktat-maven-plugin/src/test/kotlin/org/cqfn/diktat/plugin/maven/DiktatBaseMojoTest.kt @@ -11,7 +11,6 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.jupiter.api.Assertions -import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.Path import kotlin.io.path.createTempFile import kotlin.io.path.div @@ -19,7 +18,6 @@ import kotlin.io.path.div /** * Tests for mojo configuration. NB: this tests are using Junit4, because maven-plugin-testing-harness doesn't support 5. */ -@OptIn(ExperimentalPathApi::class) @Suppress("LongMethod", "TOO_LONG_FUNCTION") @Ignore class DiktatBaseMojoTest { @@ -74,7 +72,6 @@ class DiktatBaseMojoTest { val mavenProject = projectBuilder.build(pom, buildingRequest).project val diktatCheckMojo = mojoRule.lookupConfiguredMojo(mavenProject, "check") as DiktatCheckMojo - Assertions.assertFalse(diktatCheckMojo.debug) Assertions.assertEquals("diktat-analysis.yml", diktatCheckMojo.diktatConfigFile) Assertions.assertIterableEquals(listOf(pom.parentFile.toPath() / "src"), diktatCheckMojo.inputs.map { Path(it) }) Assertions.assertTrue(diktatCheckMojo.excludes.isEmpty()) @@ -100,7 +97,6 @@ class DiktatBaseMojoTest { org.cqfn.diktat diktat-maven-plugin - true my-diktat-config.yml ${'$'}{project.basedir}/src/main/kotlin @@ -126,7 +122,6 @@ class DiktatBaseMojoTest { val mavenProject = projectBuilder.build(pom, buildingRequest).project val diktatCheckMojo = mojoRule.lookupConfiguredMojo(mavenProject, "check") as DiktatCheckMojo - Assertions.assertTrue(diktatCheckMojo.debug) Assertions.assertEquals("my-diktat-config.yml", diktatCheckMojo.diktatConfigFile) Assertions.assertIterableEquals( listOf(pom.parentFile.toPath() / "src/main/kotlin", pom.parentFile.toPath() / "src/test/kotlin"), diff --git a/diktat-rules/build.gradle.kts b/diktat-rules/build.gradle.kts index 066881e477..80af533fec 100644 --- a/diktat-rules/build.gradle.kts +++ b/diktat-rules/build.gradle.kts @@ -35,17 +35,12 @@ dependencies { testImplementation(libs.kotlin.reflect) } -kotlin { - sourceSets.main { - kotlin.srcDir("build/generated/ksp/main/kotlin") +project.afterEvaluate { + tasks.named("kspKotlin") { + // not clear issue that :kspKotlin is up-to-date, but generated files are missed + outputs.upToDateWhen { false } } -} - -idea { - module { - // Not using += due to https://github.com/gradle/gradle/issues/8749 - sourceDirs = sourceDirs + file("build/generated/ksp/main/kotlin") // or tasks["kspKotlin"].destination - testSourceDirs = testSourceDirs + file("build/generated/ksp/test/kotlin") - generatedSourceDirs = generatedSourceDirs + file("build/generated/ksp/main/kotlin") + file("build/generated/ksp/test/kotlin") + tasks.named("test") { + dependsOn(tasks.named("kspKotlin")) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index f7dfe7a9f1..de7dac4a29 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -97,7 +97,6 @@ import java.io.File * @param diktatConfigFile the configuration file where all configurations for * inspections and rules are stored. */ -@Suppress("ForbiddenComment") class DiktatRuleSetProvider(private val diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) { private val possibleConfigs: Sequence = sequence { yield(resolveDefaultConfig()) @@ -135,7 +134,6 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = DIKTAT_ANALYS * of the rules have state or are not thread-safe - a new [DiktatRuleSet] must * be created). * - * TODO: comments for 0.47.x * 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 diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt index 743fd2ab75..95ee973046 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt @@ -3,6 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter3 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.ENUMS_SEPARATED import org.cqfn.diktat.ruleset.rules.DiktatRule +import org.cqfn.diktat.ruleset.utils.AstNodePredicate import org.cqfn.diktat.ruleset.utils.allSiblings import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType @@ -106,7 +107,7 @@ class EnumsSeparated(configRules: List) : DiktatRule( } } - private fun ASTNode.findLatestTreePrevMatching(predicate: (ASTNode) -> Boolean): ASTNode { + private fun ASTNode.findLatestTreePrevMatching(predicate: AstNodePredicate): ASTNode { val result = this.treePrev return if (predicate(result)) result else result.findLatestTreePrevMatching(predicate) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt index d80542f6b8..78ab577d7e 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt @@ -35,6 +35,7 @@ import org.cqfn.diktat.ruleset.utils.indentation.ValueParameterListChecker import org.cqfn.diktat.ruleset.utils.lastIndent import org.cqfn.diktat.ruleset.utils.leadingSpaceCount import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine +import org.cqfn.diktat.ruleset.utils.visit import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION @@ -65,7 +66,6 @@ import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline -import com.pinterest.ktlint.core.ast.visit import mu.KotlinLogging import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace @@ -186,7 +186,6 @@ class IndentationRule(configRules: List) : DiktatRule( */ private fun checkIndentation(node: ASTNode) = with(IndentContext(configuration)) { - @Suppress("Deprecation") node.visit { astNode -> checkAndReset(astNode) val indentationIncrement = astNode.getIndentationIncrement() diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt index 76478f76b4..a6f351a458 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt @@ -18,7 +18,6 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.isAnnotatedWithIgnoredAnnotation import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming -import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.ast.ElementType import com.pinterest.ktlint.core.ast.ElementType.ANDAND import com.pinterest.ktlint.core.ast.ElementType.ANNOTATED_EXPRESSION @@ -66,14 +65,26 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtParameterList import org.jetbrains.kotlin.psi.psiUtil.children import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.psi.stubs.elements.KtFileElementType import java.util.Locale +/** + * AST node visitor which accepts the node and, optionally, returns a value. + */ +typealias AstNodeVisitor = (node: ASTNode) -> T + +/** + * The predicate which accepts a node and returns a `boolean` value. + */ +typealias AstNodePredicate = AstNodeVisitor + /** * A class that represents result of nodes swapping. [oldNodes] should always have same size as [newNodes] * @@ -458,8 +469,8 @@ fun ASTNode.findAllDescendantsWithSpecificType(elementType: IElementType, withSe * This method performs tree traversal and returns all nodes which satisfy the condition */ fun ASTNode.findAllNodesWithCondition(withSelf: Boolean = true, - excludeChildrenCondition: ((ASTNode) -> Boolean) = { false }, - condition: (ASTNode) -> Boolean, + excludeChildrenCondition: AstNodePredicate = { false }, + condition: AstNodePredicate, ): List { val result = if (condition(this) && withSelf) mutableListOf(this) else mutableListOf() return result + this.getChildren(null) @@ -490,7 +501,7 @@ fun ASTNode.findChildrenMatching(elementType: IElementType? = null, * Check if this node has any children of optional type matching the predicate */ fun ASTNode.hasChildMatching(elementType: IElementType? = null, - predicate: (ASTNode) -> Boolean): Boolean = + predicate: AstNodePredicate): Boolean = findChildrenMatching(elementType, predicate).isNotEmpty() /** @@ -785,7 +796,7 @@ fun ASTNode.findAllNodesOnLine( */ fun ASTNode.findAllNodesWithConditionOnLine( line: Int, - condition: (ASTNode) -> Boolean + condition: AstNodePredicate ): List? = this.findAllNodesOnLine(line)?.filter(condition) /** @@ -793,10 +804,24 @@ fun ASTNode.findAllNodesWithConditionOnLine( * * @return name of the file [this] node belongs to */ -fun ASTNode.getFilePath(): String = getRootNode().also { - require(it.elementType == FILE) { "Root node type is not FILE, but ${KtLint.FILE_PATH_USER_DATA_KEY} is present in user_data only in FILE nodes" } -}.getUserData(KtLint.FILE_PATH_USER_DATA_KEY).let { - requireNotNull(it) { "File path is not present in user data" } +fun ASTNode.getFilePath(): String = run { + @Suppress("Deprecation") + val key = com.pinterest.ktlint.core.KtLint.FILE_PATH_USER_DATA_KEY + val rootNode = getRootNode() + .also { + require(it.elementType == KtFileElementType.INSTANCE) { "Root node type is not FILE, but $key can present in user_data only in FILE nodes" } + } + + rootNode.getUserData(key) + ?: run { + // KtLint doesn't set file path for snippets + // will take a file name from KtFile + // it doesn't work for all cases since KtLint creates KtFile using a file name, not a file path + // raised: https://github.com/pinterest/ktlint/issues/1921 + requireNotNull(rootNode.psi as? KtFile) { + "Root node type is not ${KtFile::class}" + }.name + } } /** @@ -899,6 +924,35 @@ fun ASTNode.isBooleanExpression(): Boolean = } } +/** + * Before _KtLint_ **0.48**, this extension used to reside in the + * `com.pinterest.ktlint.core.ast` package. + * Because _KtLint_ **0.47** has changed the way syntax nodes are traversed (see + * the diagrams [here](https://github.com/saveourtool/diktat/issues/1538)), the + * codebase of _KtLint_ no longer needs it: in most cases, it's sufficient to + * invoke + * + * ```kotlin + * visitor(this) + * ``` + * + * and rely on _KtLint_ to invoke the same visitor (which is usually a `Rule`) + * on this node's children. + * Still, _Diktat_ sometimes needs exactly this old behaviour. + * + * @param visitor the visitor to recursively traverse this node as well as its + * children. + * @since 1.2.5 + */ +fun ASTNode.visit(visitor: AstNodeVisitor) { + visitor(this) + @Suppress("NULLABLE_PROPERTY_TYPE") + val filter: TokenSet? = null + getChildren(filter).forEach { child -> + child.visit(visitor) + } +} + /** * @return whether this PSI element is a long string template entry. * @since 1.2.4 diff --git a/diktat-ruleset/build.gradle.kts b/diktat-ruleset/build.gradle.kts index b1b72bbbca..b0c1d2b58d 100644 --- a/diktat-ruleset/build.gradle.kts +++ b/diktat-ruleset/build.gradle.kts @@ -60,6 +60,9 @@ tasks { build { dependsOn(shadowJar) } + test { + dependsOn(shadowJar) + } } publishing { diff --git a/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt deleted file mode 100644 index 0a3b744bc7..0000000000 --- a/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderSpi.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.cqfn.diktat.ruleset.rules - -import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint -import com.pinterest.ktlint.core.RuleSet -import com.pinterest.ktlint.core.RuleSetProvider -import com.pinterest.ktlint.core.initKtLintKLogger -import mu.KotlinLogging -import org.slf4j.Logger - -/** - * [RuleSetProvider] that provides diKTat ruleset. - * - * 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. - * - * This class is registered in [resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider] - */ -class DiktatRuleSetProviderSpi : RuleSetProvider { - init { - // Need to init KtLint logger to set log level from CLI - KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).initKtLintKLogger() - } - - override fun get(): RuleSet = DiktatRuleSetProvider().invoke().toKtLint() -} diff --git a/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderV2Spi.kt b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderV2Spi.kt new file mode 100644 index 0000000000..7d4d5b8129 --- /dev/null +++ b/diktat-ruleset/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProviderV2Spi.kt @@ -0,0 +1,37 @@ +package org.cqfn.diktat.ruleset.rules + +import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ktlint.KtLintRuleWrapper.Companion.toKtLint +import com.pinterest.ktlint.core.RuleProvider +import com.pinterest.ktlint.core.RuleSetProviderV2 +import com.pinterest.ktlint.core.initKtLintKLogger +import mu.KotlinLogging +import org.slf4j.Logger + +/** + * [RuleSetProviderV2] that provides diKTat ruleset. + * + * 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. + * + * This class is registered in [resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProviderV2] + * + * The no-argument constructor is used by the Java SPI interface. + */ +class DiktatRuleSetProviderV2Spi : RuleSetProviderV2( + id = DIKTAT_RULE_SET_ID, + about = About( + maintainer = "Diktat", + description = "Strict coding standard for Kotlin and a custom set of rules for detecting code smells, code style issues, and bugs", + license = "https://github.com/saveourtool/diktat/blob/master/LICENSE", + repositoryUrl = "https://github.com/saveourtool/diktat", + issueTrackerUrl = "https://github.com/saveourtool/diktat/issues", + ), +) { + 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() +} diff --git a/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider b/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider deleted file mode 100644 index ea992bfe07..0000000000 --- a/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider +++ /dev/null @@ -1 +0,0 @@ -org.cqfn.diktat.ruleset.rules.DiktatRuleSetProviderSpi diff --git a/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProviderV2 b/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProviderV2 new file mode 100644 index 0000000000..143ed2e22b --- /dev/null +++ b/diktat-ruleset/src/main/resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProviderV2 @@ -0,0 +1 @@ +org.cqfn.diktat.ruleset.rules.DiktatRuleSetProviderV2Spi diff --git a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSaveSmokeTest.kt b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSaveSmokeTest.kt index bc8e445b07..95129883e0 100644 --- a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSaveSmokeTest.kt +++ b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSaveSmokeTest.kt @@ -78,12 +78,12 @@ class DiktatSaveSmokeTest : DiktatSmokeTestBase() { * On Windows, ktlint is often unable to relativize paths * (see https://github.com/pinterest/ktlint/issues/1608). * + * Also, ktlint needs `.editorconfig` to disable standard rules + * * So let's force the temporary directory to be the * sub-directory of the project root. */ - if (System.getProperty("os.name").isWindows()) { - temporaryDirectory(baseDirectoryPath / WINDOWS_TEMP_DIRECTORY) - } + temporaryDirectory(baseDirectoryPath / TEMP_DIRECTORY) } val saveProcess = processBuilder.start() @@ -131,7 +131,7 @@ class DiktatSaveSmokeTest : DiktatSmokeTestBase() { private val logger = KotlinLogging.logger {} private const val BASE_DIRECTORY = "src/test/resources/test/smoke" private const val SAVE_VERSION: String = "0.3.4" - private const val WINDOWS_TEMP_DIRECTORY = ".save-cli" + private const val TEMP_DIRECTORY = ".save-cli" private val baseDirectoryPath = Path(BASE_DIRECTORY).absolute() private fun getSaveForCurrentOs(): String { @@ -191,15 +191,13 @@ class DiktatSaveSmokeTest : DiktatSmokeTestBase() { val diktat = baseDirectoryPath / DIKTAT_FAT_JAR val save = baseDirectoryPath / getSaveForCurrentOs() val ktlint = baseDirectoryPath / KTLINT_FAT_JAR + val tempDirectory = baseDirectoryPath / TEMP_DIRECTORY diktat.deleteIfExistsSilently() ktlint.deleteIfExistsSilently() save.deleteIfExistsSilently() - if (System.getProperty("os.name").isWindows()) { - val tempDirectory = baseDirectoryPath / WINDOWS_TEMP_DIRECTORY - tempDirectory.deleteIfExistsRecursively() - } + tempDirectory.deleteIfExistsRecursively() } } } diff --git a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt index b71a3baa5a..6787c56072 100644 --- a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt +++ b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTestBase.kt @@ -238,8 +238,8 @@ abstract class DiktatSmokeTestBase { /* * This 2nd `MISSING_KDOC_ON_FUNCTION` is a duplicate caused by * https://github.com/saveourtool/diktat/issues/1538. - * LintError(6, 5, "$DIKTAT_RULE_SET_ID:${KdocMethods.NAME_ID}", "${MISSING_KDOC_ON_FUNCTION.warnText()} foo", false), */ + LintError(6, 5, "$DIKTAT_RULE_SET_ID:${KdocMethods.NAME_ID}", "${MISSING_KDOC_ON_FUNCTION.warnText()} foo", false), LintError(9, 3, "$DIKTAT_RULE_SET_ID:${EmptyBlock.NAME_ID}", EMPTY_BLOCK_STRUCTURE_ERROR.warnText() + " empty blocks are forbidden unless it is function with override keyword", false), LintError(12, 10, "$DIKTAT_RULE_SET_ID:${KdocFormatting.NAME_ID}", "${KDOC_NO_EMPTY_TAGS.warnText()} @return", false), diff --git a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/util/SmokeTestUtils.kt b/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/util/SmokeTestUtils.kt deleted file mode 100644 index 8b69310762..0000000000 --- a/diktat-ruleset/src/test/kotlin/org/cqfn/diktat/util/SmokeTestUtils.kt +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Utility classes and methods for tests - */ - -package org.cqfn.diktat.util - -import java.io.File -import java.nio.file.NoSuchFileException -import java.nio.file.Path -import kotlin.io.path.isDirectory -import kotlin.io.path.isSameFileAs - -/** - * @receiver the 1st operand. - * @param other the 2nd operand. - * @return `true` if, and only if, the two paths locate the same `JAVA_HOME`. - */ -internal fun Path.isSameJavaHomeAs(other: Path): Boolean = - isDirectory() && - (isSameFileAsSafe(other) || - resolve("jre").isSameFileAsSafe(other) || - other.resolve("jre").isSameFileAsSafe(this)) - -/** - * The same as [Path.isSameFileAs], but doesn't throw any [NoSuchFileException] - * if either of the operands doesn't exist. - * - * @receiver the 1st operand. - * @param other the 2nd operand. - * @return `true` if, and only if, the two paths locate the same file. - * @see Path.isSameFileAs - */ -internal fun Path.isSameFileAsSafe(other: Path): Boolean = - try { - isSameFileAs(other) - } catch (_: NoSuchFileException) { - false - } - -/** - * Prepends the `PATH` of this process builder with [pathEntry]. - * - * @param pathEntry the entry to be prepended to the `PATH`. - */ -internal fun ProcessBuilder.prependPath(pathEntry: Path) { - require(pathEntry.isDirectory()) { - "$pathEntry is not a directory" - } - - val environment = environment() - - val defaultPathKey = "PATH" - val defaultWindowsPathKey = "Path" - - val pathKey = when { - /*- - * Keys of the Windows environment are case-insensitive ("PATH" == "Path"). - * Keys of the Java interface to the environment are not ("PATH" != "Path"). - * This is an attempt to work around the inconsistency. - */ - System.getProperty("os.name").startsWith("Windows") -> environment.keys.firstOrNull { key -> - key.equals(defaultPathKey, ignoreCase = true) - } ?: defaultWindowsPathKey - - else -> defaultPathKey - } - - val pathSeparator = File.pathSeparatorChar - val oldPath = environment[pathKey] - - val newPath = when { - oldPath.isNullOrEmpty() -> pathEntry.toString() - else -> "$pathEntry$pathSeparator$oldPath" - } - - environment[pathKey] = newPath -} diff --git a/diktat-ruleset/src/test/resources/test/smoke/.editorconfig b/diktat-ruleset/src/test/resources/test/smoke/.editorconfig new file mode 100644 index 0000000000..475d1b02b7 --- /dev/null +++ b/diktat-ruleset/src/test/resources/test/smoke/.editorconfig @@ -0,0 +1,8 @@ +# https://editorconfig.org +root = true +[{*.kt,*.kts}] +# disable ktlint rules +ktlint_standard = disabled +ktlint_experimental = disabled +ktlint_test = disabled +ktlint_custom = disabled \ No newline at end of file diff --git a/diktat-ruleset/src/test/resources/test/smoke/save.toml b/diktat-ruleset/src/test/resources/test/smoke/save.toml index ba0e3acee4..f5bcdde6a3 100644 --- a/diktat-ruleset/src/test/resources/test/smoke/save.toml +++ b/diktat-ruleset/src/test/resources/test/smoke/save.toml @@ -1,5 +1,5 @@ [general] -execCmd="java -showversion -jar ktlint --debug --verbose -R diktat.jar" +execCmd="java -showversion -jar ktlint --log-level=debug -R diktat.jar" tags = ["smokeTest"] description = "SmokeTest" suiteName = "SmokeTest" @@ -9,7 +9,7 @@ timeOutMillis = 3600000 ["fix and warn"] ["fix and warn".fix] - execFlags="--disabled_rules=standard,experimental,test,custom -F" + execFlags="-F" ["fix and warn".warn] lineCaptureGroup = 1 columnCaptureGroup = 2 diff --git a/diktat-ruleset/src/test/resources/test/smoke/src/main/kotlin/save.toml b/diktat-ruleset/src/test/resources/test/smoke/src/main/kotlin/save.toml index f4d8eb501e..467f9d120d 100644 --- a/diktat-ruleset/src/test/resources/test/smoke/src/main/kotlin/save.toml +++ b/diktat-ruleset/src/test/resources/test/smoke/src/main/kotlin/save.toml @@ -1,5 +1,5 @@ [general] -execCmd="java -showversion -jar ktlint --debug --verbose -R diktat.jar" +execCmd="java -showversion -jar ktlint --log-level=debug -R diktat.jar" tags = ["smokeTest"] description = "SmokeTest" suiteName = "SmokeTest" @@ -8,7 +8,7 @@ expectedWarningsPattern = "// ;warn:?(.*):(\\d*): (.+)" ["fix and warn"] ["fix and warn".fix] - execFlags="--disabled_rules=standard,experimental,test,custom -F" + execFlags="-F" ["fix and warn".warn] actualWarningsPattern = "(\\w+\\..+):(\\d+):(\\d+): (\\[.*\\].*)$" exactWarningsMatch = false diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt index 077a809850..4b1c9ccb10 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestComparatorUnit.kt @@ -1,6 +1,7 @@ package org.cqfn.diktat.test.framework.processing import org.cqfn.diktat.test.framework.util.readTextOrNull +import org.cqfn.diktat.test.framework.util.toUnixEndLines import mu.KotlinLogging import java.nio.file.Path import kotlin.io.path.isRegularFile @@ -77,7 +78,7 @@ class TestComparatorUnit( expectedContent = "// $expectedFile is a regular file: ${expectedFile.isRegularFile()}") } - val actualFileContent = function(testFile) + val actualFileContent = function(testFile).toUnixEndLines() val expectedFileContent = expectedFile.readTextOrNull().orEmpty() val comparator = FileComparator( diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt index 5f8b1d31a0..bda46c5714 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/util/TestUtils.kt @@ -274,13 +274,18 @@ fun String?.isWindows(): Boolean { return this != null && startsWith("Windows") } +/** + * @return original [String] with unix end lines + */ +fun String.toUnixEndLines(): String = replace("\r\n", "\n").replace("\r", "\n") + /** * @receiver the file whose content is to be read. * @return file content as a single [String], or null if an I/O error * has occurred. */ fun Path.readTextOrNull(): String? = try { - readText(StandardCharsets.UTF_8).replace("\r\n", "\n").replace("\r", "\n") + readText(StandardCharsets.UTF_8).toUnixEndLines() } catch (e: IOException) { logger.error(e) { "Not able to read file: $this" } null diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 72722f4d60..e76ecac37d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "1.8.20" kotlin-ksp = "1.8.20-1.0.10" serialization = "1.5.0" -ktlint = "0.46.1" +ktlint = "0.48.2" junit = "5.9.2" junit-platfrom = "1.9.2" guava = "31.1-jre"