diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 74d1f804d9..52f66a852d 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -272,7 +272,7 @@ class KtLintRuleEngineTest { @Nested inner class `Format with KtLintRuleEngine` { @Nested - inner class `Given a file that does not contain an error` { + inner class `Given a file that containing some errors` { @Test fun `Given defaultAutocorrect is not set`( @TempDir @@ -402,7 +402,7 @@ class KtLintRuleEngineTest { } @Nested - inner class `Given a kotlin code snippet that does contain an indentation error` { + inner class `Given a kotlin code snippet containing some errors` { @Test fun `Given defaultAutocorrect is not set`() { val lintErrors = mutableListOf() @@ -511,7 +511,7 @@ class KtLintRuleEngineTest { } @Nested - inner class `Given a kotlin script code snippet that does contain an indentation error` { + inner class `Given a kotlin script code snippet containing some errors` { @Test fun `Given defaultAutocorrect is not set`() { val lintErrors = mutableListOf() @@ -662,6 +662,51 @@ class KtLintRuleEngineTest { """.trimIndent(), ) } + + @Test + fun `Issue 2747 - Given some code with crlf separators instead of lfs, but not containing any lint error, then do no reformat the line separators`( + @TempDir + tempDir: Path, + ) { + val codeWithCrlfSeparators = + """ + fun bar() { + // FOO + // BAR + } + """.trimIndent().replace("\n", "\r\n") + val filePath = "$tempDir/Code.kt" + FileWriter(filePath).use { + it.write(codeWithCrlfSeparators) + } + + val lintErrors = mutableListOf() + val actual = + KtLintRuleEngine( + ruleProviders = + setOf( + RuleProvider { IndentationRule() }, + RuleProvider { RuleWithAutocorrectApproveHandler() }, + RuleProvider { RuleWithoutAutocorrectApproveHandler() }, + ), + editorConfigOverride = + EditorConfigOverride.from( + // Do not set END_OF_LINE_PROPERTY explicitly! + RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER.createRuleExecutionEditorConfigProperty() to RuleExecution.enabled, + RULE_WITH_AUTOCORRECT_APPROVE_HANDLER.createRuleExecutionEditorConfigProperty() to RuleExecution.enabled, + ), + fileSystem = ktlintTestFileSystem.fileSystem, + ).format( + code = Code.fromFile(File(filePath)), + defaultAutocorrect = true, + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } + + assertThat(lintErrors).isEmpty() + assertThat(actual).isEqualTo(codeWithCrlfSeparators) + } } @Test diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 35c073b2be..c8418942db 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -10,6 +10,7 @@ import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPERTY import io.github.oshai.kotlinlogging.KotlinLogging import org.ec4j.core.model.PropertyType +import org.jetbrains.kotlin.util.prefixIfNot private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() @@ -30,7 +31,7 @@ internal class CodeFormatter( .sortedWith(lintErrorLineAndColumnComparator { it.first }) .forEach { (e, corrected) -> callback(e, corrected) } - return (code.utf8Bom() + formattedCode).also { + return (formattedCode.prefixIfNot(code.utf8Bom())).also { LOGGER.debug { "Finished with processing file '${code.fileNameOrStdin()}'" } } } @@ -71,8 +72,13 @@ internal class CodeFormatter( } } } - val lineSeparator = code.determineLineSeparator(editorConfig[END_OF_LINE_PROPERTY]) - return Pair(formattedCode(lineSeparator), errors) + return if (mutated || formatRunCount > 1) { + val lineSeparator = code.determineLineSeparator(editorConfig[END_OF_LINE_PROPERTY]) + Pair(formattedCode(lineSeparator), errors) + } else { + // None of the format runs has found + Pair(code.content, errors) + } } } @@ -149,9 +155,9 @@ internal class CodeFormatter( eolEditorConfigProperty == PropertyType.EndOfLineValue.crlf || eolEditorConfigProperty != PropertyType.EndOfLineValue.lf && doesNotContain('\r') -> - "\r\n".also { LOGGER.debug { "line separator: ${eolEditorConfigProperty.name} --> CRLF" } } + "\r\n".also { LOGGER.trace { "line separator: ${eolEditorConfigProperty.name} --> CRLF" } } - else -> "\n".also { LOGGER.debug { "line separator: ${eolEditorConfigProperty.name} --> LF" } } + else -> "\n".also { LOGGER.trace { "line separator: ${eolEditorConfigProperty.name} --> LF" } } } private fun Code.doesNotContain(char: Char) = content.lastIndexOf(char) != -1