diff --git a/CHANGELOG.md b/CHANGELOG.md index 7318edda58..04be99080b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Update Dokka to `1.6.0` release - Apply ktlint experimental rules on the ktlint code base itself. - Update shadow plugin to `7.1.1` release +- Add Kotlin-logging backed by logback as logging framework ([#589](https://github.com/pinterest/ktlint/issues/589)) ### Removed diff --git a/build.gradle b/build.gradle index edc20df57d..72b8e18fc1 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,9 @@ ext.deps = [ 'klob' : 'com.github.shyiko.klob:klob:0.2.1', ec4j : 'org.ec4j.core:ec4j-core:0.3.0', 'picocli' : 'info.picocli:picocli:3.9.6', + 'logging' : 'io.github.microutils:kotlin-logging-jvm:2.1.21', + // Use logback-classic as the logger for kotlin-logging / slf4j as it allow changing the log level at runtime. + 'logback' : 'ch.qos.logback:logback-classic:1.2.9', // Testing libraries 'junit' : 'junit:junit:4.13.1', 'junit5Api' : 'org.junit.jupiter:junit-jupiter-api:5.8.2', diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b369ce2414..2e7feb9bb9 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -35,7 +35,11 @@ - + + + + + @@ -136,6 +140,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ktlint-core/build.gradle b/ktlint-core/build.gradle index 5f08694db4..ff655f81ab 100644 --- a/ktlint-core/build.gradle +++ b/ktlint-core/build.gradle @@ -6,6 +6,8 @@ plugins { dependencies { api deps.kotlin.compiler api deps.ec4j + api deps.logging + api deps.logback testImplementation deps.junit testImplementation deps.assertj diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLintKLoggerInitializer.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLintKLoggerInitializer.kt new file mode 100644 index 0000000000..74a53da761 --- /dev/null +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLintKLoggerInitializer.kt @@ -0,0 +1,56 @@ +package com.pinterest.ktlint.core + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import mu.KLogger +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue + +public enum class LogLevel { TRACE, DEBUG, INFO } + +public var logLevel: LogLevel? = null + +@Deprecated("Environment variable is replace by new variables below") +private const val KTLINT_DEBUG = "KTLINT_DEBUG" + +// Via command line parameters "--trace" and "--print-ast" the end user of ktlint can change the logging behavior. As +// unit tests are not invoked via the main ktlint runtime, those command line parameters can not be used to change the +// logging behavior while running the unit tests. Instead, the environment variables below can be used by ktlint +// developers to change the logging behavior. Note, when set the environment variables also affect the runtinme of +// ktlint. As of that the name of the variables start with KTLINT_UNIT_TEST to clarify the intent. +public const val KTLINT_UNIT_TEST_DUMP_AST = "KTLINT_UNIT_TEST_DUMP_AST" +public const val KTLINT_UNIT_TEST_TRACE = "KTLINT_UNIT_TEST_TRACE" +public const val KTLINT_UNIT_TEST_ON_PROPERTY = "ON" + +public fun KLogger.initKtLintKLogger(): KLogger = + also { logger -> + System + .getenv(KTLINT_UNIT_TEST_TRACE) + .orEmpty() + .equals(KTLINT_UNIT_TEST_ON_PROPERTY, ignoreCase = true) + .ifTrue { + // The log level of the kotlin-logging framework can only be altered by modifying the underling logging + // library. Also note that the default SLF4J implementation does not allow the log level to be changes. + // Therefore, a fall back on the logback-core is required. See + // https://github.com/MicroUtils/kotlin-logging/issues/20 + logger.trace { "Enable TRACE logging as System environment variable $KTLINT_UNIT_TEST_TRACE is set to 'on'" } + logLevel = LogLevel.TRACE + } + if (logLevel == LogLevel.TRACE) { + (logger.underlyingLogger as Logger).level = Level.TRACE + } + + System + .getenv(KTLINT_DEBUG) + .orEmpty() + .takeIf { it.isNotEmpty() } + ?.let { + logger.error { + """ + System environment variable $KTLINT_DEBUG is no longer used to change the logging behavior while running unit tests. + Now set one or more of environment variables below: + $KTLINT_UNIT_TEST_TRACE=[on|off] + $KTLINT_UNIT_TEST_DUMP_AST=[on|off] + """.trimIndent() + } + } + } diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/BaselineSupport.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/BaselineSupport.kt index dfabf21be6..d5e65938fa 100644 --- a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/BaselineSupport.kt +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/BaselineSupport.kt @@ -1,15 +1,19 @@ package com.pinterest.ktlint.core.internal import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.core.initKtLintKLogger import java.io.File import java.io.IOException import java.io.InputStream import java.nio.file.Paths import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.ParserConfigurationException +import mu.KotlinLogging import org.w3c.dom.Element import org.xml.sax.SAXException +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + /** * Loads the baseline file if one is provided. * @@ -31,13 +35,13 @@ public fun loadBaseline( baselineRules = parseBaseline(baselineFile.inputStream()) baselineGenerationNeeded = false } catch (e: IOException) { - System.err.println("Unable to parse baseline file: $baselineFilePath") + logger.error { "Unable to parse baseline file: $baselineFilePath" } baselineGenerationNeeded = true } catch (e: ParserConfigurationException) { - System.err.println("Unable to parse baseline file: $baselineFilePath") + logger.error { "Unable to parse baseline file: $baselineFilePath" } baselineGenerationNeeded = true } catch (e: SAXException) { - System.err.println("Unable to parse baseline file: $baselineFilePath") + logger.error { "Unable to parse baseline file: $baselineFilePath" } baselineGenerationNeeded = true } } diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/EditorConfigLoader.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/EditorConfigLoader.kt index a625964550..e075b5d574 100644 --- a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/EditorConfigLoader.kt +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/EditorConfigLoader.kt @@ -4,9 +4,11 @@ import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.api.EditorConfigProperties import com.pinterest.ktlint.core.api.FeatureInAlphaState import com.pinterest.ktlint.core.api.UsesEditorConfigProperties +import com.pinterest.ktlint.core.initKtLintKLogger import java.nio.charset.StandardCharsets import java.nio.file.FileSystem import java.nio.file.Path +import mu.KotlinLogging import org.ec4j.core.EditorConfigLoader import org.ec4j.core.PropertyTypeRegistry import org.ec4j.core.Resource @@ -15,6 +17,8 @@ import org.ec4j.core.model.Property import org.ec4j.core.model.PropertyType import org.ec4j.core.model.Version +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + /** * Map contains [UsesEditorConfigProperties.EditorConfigProperty] and related * [PropertyType.PropertyValue] entries to add/replace loaded from `.editorconfig` files values. @@ -82,8 +86,6 @@ public class EditorConfigLoader( else -> filePath } - if (debug) println("Resolving .editorconfig files for $normalizedFilePath file path") - return propService .queryProperties( Resource.Resources.ofPath(normalizedFilePath, StandardCharsets.UTF_8) @@ -99,15 +101,13 @@ public class EditorConfigLoader( } } .also { - if (debug) { - val editorConfigValues = it - .map { entry -> - "${entry.key}: ${entry.value.sourceValue}" - } + logger.trace { + it + .map { entry -> "${entry.key}: ${entry.value.sourceValue}" } .joinToString( + prefix = "Resolving .editorconfig files for $normalizedFilePath file path:\n\t", separator = ", " ) - println("Loaded .editorconfig: [$editorConfigValues]") } } } diff --git a/ktlint-ruleset-standard/build.gradle b/ktlint-ruleset-standard/build.gradle index 5f53aa34ea..759fbc6eb1 100644 --- a/ktlint-ruleset-standard/build.gradle +++ b/ktlint-ruleset-standard/build.gradle @@ -5,6 +5,7 @@ plugins { dependencies { implementation project(':ktlint-core') + implementation deps.logging testImplementation project(':ktlint-test') testImplementation deps.junit diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt index cf423f4ac6..d11fac580d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt @@ -7,17 +7,21 @@ import com.pinterest.ktlint.core.api.FeatureInAlphaState import com.pinterest.ktlint.core.api.UsesEditorConfigProperties import com.pinterest.ktlint.core.ast.ElementType import com.pinterest.ktlint.core.ast.isRoot +import com.pinterest.ktlint.core.initKtLintKLogger import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule.Companion.ASCII_PATTERN import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule.Companion.IDEA_PATTERN import com.pinterest.ktlint.ruleset.standard.internal.importordering.ImportSorter import com.pinterest.ktlint.ruleset.standard.internal.importordering.PatternEntry import com.pinterest.ktlint.ruleset.standard.internal.importordering.parseImportsLayout +import mu.KotlinLogging import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.psi.KtImportDirective +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + /** * Import ordering is configured via EditorConfig's property `ij_kotlin_imports_layout`, so the Kotlin IDE plugin also recongizes it. Supported values: * * "*,java.**,javax.**,kotlin.**,^" - default IntelliJ IDEA's order, see [IDEA_PATTERN] @@ -85,20 +89,14 @@ public class ImportOrderingRule : "Import layout must contain at least one entry of a wildcard symbol (*)" ) value == "idea" -> { - println( - "[WARNING] `idea` is deprecated! Please use `*,java.**,javax.**,kotlin.**,^` instead" + - " to ensure that the Kotlin IDE plugin recognizes the value" - ) + logger.warn { "`idea` is deprecated! Please use `*,java.**,javax.**,kotlin.**,^` instead to ensure that the Kotlin IDE plugin recognizes the value" } PropertyType.PropertyValue.valid( value, IDEA_PATTERN ) } value == "ascii" -> { - println( - "[WARNING] `ascii` is deprecated! Please use `*` instead" + - " to ensure that the Kotlin IDE plugin recognizes the value" - ) + logger.warn { "`ascii` is deprecated! Please use `*` instead to ensure that the Kotlin IDE plugin recognizes the value" } PropertyType.PropertyValue.valid( value, ASCII_PATTERN @@ -252,10 +250,7 @@ public class ImportOrderingRule : private fun EditorConfigProperties.resolveImportsLayout( android: Boolean ): List = if (containsKey(KTLINT_CUSTOM_IMPORTS_LAYOUT_PROPERTY_NAME)) { - println( - "[WARNING] `kotlin_imports_layout` is deprecated! Please use `ij_kotlin_imports_layout` to ensure" + - " that the Kotlin IDE plugin and ktlint use same imports layout" - ) + logger.warn { "`kotlin_imports_layout` is deprecated! Please use `ij_kotlin_imports_layout` to ensure that the Kotlin IDE plugin and ktlint use same imports layout" } getEditorConfigValue(ktlintCustomImportsLayoutProperty, android) } else { getEditorConfigValue(ideaImportsLayoutProperty, android) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt index 5a8aed5357..636c6d8337 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt @@ -82,8 +82,10 @@ import com.pinterest.ktlint.core.ast.prevSibling import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe import com.pinterest.ktlint.core.ast.visit +import com.pinterest.ktlint.core.initKtLintKLogger import java.util.Deque import java.util.LinkedList +import mu.KotlinLogging import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement @@ -93,6 +95,8 @@ import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.KtSuperTypeList import org.jetbrains.kotlin.psi.psiUtil.leaves +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + /** * ktlint's rule that checks & corrects indentation. * @@ -112,16 +116,6 @@ class IndentationRule : Rule( ) { private companion object { - // run `KTLINT_DEBUG=experimental/indent ktlint ...` to enable debug output - private val debugMode = - (System.getenv("KTLINT_DEBUG") ?: "").split(",").contains("experimental/indent") - - private inline fun debug(msg: () -> String) { - if (debugMode) { - System.err.println("[DEBUG] indent: ${msg()}") - } - } - private val lTokenSet = TokenSet.create(LPAR, LBRACE, LBRACKET, LT) private val rTokenSet = TokenSet.create(RPAR, RBRACE, RBRACKET, GT) private val matchingRToken = @@ -138,10 +132,6 @@ class IndentationRule : Rule( expectedIndent = 0 } - private inline fun debug(msg: () -> String) { - Companion.debug { "$line: " + msg() } - } - override fun visit( node: ASTNode, autoCorrect: Boolean, @@ -152,7 +142,7 @@ class IndentationRule : Rule( return } reset() - Companion.debug { "phase: rearrangement (auto correction ${if (autoCorrect) "on" else "off"})" } + logger.trace { "phase: rearrangement (auto correction ${if (autoCorrect) "on" else "off"})" } // step 1: insert newlines (if/where needed) var emitted = false rearrange(node, autoCorrect) { offset, errorMessage, canBeAutoCorrected -> @@ -160,13 +150,17 @@ class IndentationRule : Rule( emit(offset, errorMessage, canBeAutoCorrected) } if (emitted && autoCorrect) { - Companion.debug { + logger.trace { "indenting:\n" + - node.text.split("\n").mapIndexed { i, v -> "\t${i + 1}: $v" }.joinToString("\n") + node + .text + .split("\n") + .mapIndexed { i, v -> "\t${i + 1}: $v" } + .joinToString("\n") } } reset() - Companion.debug { "phase: indentation" } + logger.trace { "phase: indentation" } // step 2: correct indentation indent(node, autoCorrect, emit, editorConfig) @@ -378,10 +372,7 @@ class IndentationRule : Rule( n as LeafPsiElement n.rawInsertBeforeMe(LeafPsiElement(REGULAR_STRING_PART, "\n")) } - debug { - (if (!autoCorrect) "would have " else "") + - "inserted newline before (closing) \"\"\"" - } + logger.trace { "$line: " + (if (!autoCorrect) "would have " else "") + "inserted newline before (closing) \"\"\"" } } } } @@ -464,7 +455,7 @@ class IndentationRule : Rule( """Missing newline before "${node.text}"""", true ) - debug { (if (!autoCorrect) "would have " else "") + "inserted newline before ${node.text}" } + logger.trace { "$line: " + ((if (!autoCorrect) "would have " else "") + "inserted newline before ${node.text}") } if (autoCorrect) { (node.psi as LeafPsiElement).upsertWhitespaceBeforeMe("\n ") } @@ -480,7 +471,7 @@ class IndentationRule : Rule( """Missing newline after "${node.text}"""", true ) - debug { (if (!autoCorrect) "would have " else "") + "inserted newline after ${node.text}" } + logger.trace { "$line: " + (if (!autoCorrect) "would have " else "") + "inserted newline after ${node.text}" } if (autoCorrect) { (node.psi as LeafPsiElement).upsertWhitespaceAfterMe("\n ") } @@ -518,7 +509,7 @@ class IndentationRule : Rule( val leftBrace = n.takeIf { it.elementType == LBRACE } if (prevBlockLine != line && !leftBrace.isAfterLambdaArgumentOnSameLine()) { expectedIndent++ - debug { "++${n.text} -> $expectedIndent" } + logger.trace { "$line: ++${n.text} -> $expectedIndent" } } ctx.blockOpeningLineStack.push(line) } @@ -529,7 +520,7 @@ class IndentationRule : Rule( val pairedLeft = n.pairedLeft() if (prevBlockLine != blockLine && !pairedLeft.isAfterLambdaArgumentOnSameLine()) { expectedIndent-- - debug { "--on(${n.elementType}) -> $expectedIndent" } + logger.trace { "$line: --on(${n.elementType}) -> $expectedIndent" } val byKeywordOnSameLine = pairedLeft?.prevLeafOnSameLine(BY_KEYWORD) if (byKeywordOnSameLine != null && @@ -537,7 +528,7 @@ class IndentationRule : Rule( n.leavesOnSameLine(forward = true).all { it.isWhiteSpace() || it.isPartOfComment() } ) { expectedIndent-- - debug { "--on same line as by keyword ${n.text} -> $expectedIndent" } + logger.trace { "$line: --on same line as by keyword ${n.text} -> $expectedIndent" } } } } @@ -545,13 +536,13 @@ class IndentationRule : Rule( // if (n.treeParent.elementType.let { it == TYPE_PARAMETER_LIST || it == TYPE_ARGUMENT_LIST }) { expectedIndent++ - debug { "++${n.text} -> $expectedIndent" } + logger.trace { "$line: ++${n.text} -> $expectedIndent" } } GT -> // if (n.treeParent.elementType.let { it == TYPE_PARAMETER_LIST || it == TYPE_ARGUMENT_LIST }) { expectedIndent-- - debug { "--${n.text} -> $expectedIndent" } + logger.trace { "$line: --${n.text} -> $expectedIndent" } } SUPER_TYPE_LIST -> // class A : @@ -656,7 +647,7 @@ class IndentationRule : Rule( visitWhiteSpace(n, autoCorrect, emit, editorConfig) if (ctx.localAdj != 0) { expectedIndent += ctx.localAdj - debug { "++${ctx.localAdj} on whitespace containing new line (${n.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++${ctx.localAdj} on whitespace containing new line (${n.elementType}) -> $expectedIndent" } ctx.localAdj = 0 } } else if (n.isPartOf(KDOC)) { @@ -665,8 +656,8 @@ class IndentationRule : Rule( line += n.text.count { it == '\n' } } EOL_COMMENT -> - if (debugMode && n.text == "// ktlint-debug-print-expected-indent") { - debug { "expected indent: $expectedIndent" } + if (n.text == "// ktlint-debug-print-expected-indent") { + logger.trace { "$line: expected indent: $expectedIndent" } } } }, @@ -680,7 +671,7 @@ class IndentationRule : Rule( val adj = ctx.clearExitAdj(n) if (adj != null) { expectedIndent += adj - debug { "adjusted ${n.elementType} by $adj -> $expectedIndent" } + logger.trace { "$line: adjusted ${n.elementType} by $adj -> $expectedIndent" } } } ) @@ -693,7 +684,7 @@ class IndentationRule : Rule( val nextSibling = n.treeNext if (!ctx.ignored.contains(p) && nextSibling != null) { expectedIndent++ - debug { "++inside(${p.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++inside(${p.elementType}) -> $expectedIndent" } ctx.ignored.add(p) ctx.exitAdjBy(p, -1) } @@ -704,7 +695,7 @@ class IndentationRule : Rule( val p = n.treeParent if (!ctx.ignored.contains(p)) { expectedIndent++ - debug { "++inside(${p.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++inside(${p.elementType}) -> $expectedIndent" } val rOperand = n.nextSibling { sibling -> sibling.elementType != OPERATION_REFERENCE && sibling.elementType != WHITE_SPACE @@ -719,7 +710,7 @@ class IndentationRule : Rule( nextSibling.firstChildNode.elementType != CALL_EXPRESSION ) { ctx.localAdj = -1 - debug { "--inside(${nextSibling.elementType}) -> $expectedIndent" } + logger.trace { "$line: --inside(${nextSibling.elementType}) -> $expectedIndent" } ctx.exitAdjBy(p, 1) } } @@ -728,25 +719,25 @@ class IndentationRule : Rule( private fun adjustExpectedIndentInFrontOfControlBlock(n: ASTNode, ctx: IndentContext) { val nextSibling = n.treeNext expectedIndent++ - debug { "++in_front(${nextSibling.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++in_front(${nextSibling.elementType}) -> $expectedIndent" } ctx.exitAdjBy(nextSibling, -1) } private fun adjustExpectedIndentInFrontOfPropertyAccessor(n: ASTNode, ctx: IndentContext) { expectedIndent++ - debug { "++in_front(${n.treeNext.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++in_front(${n.treeNext.elementType}) -> $expectedIndent" } ctx.exitAdjBy(n.treeNext, -1) } private fun adjustExpectedIndentInFrontOfSuperTypeList(n: ASTNode, ctx: IndentContext) { expectedIndent++ - debug { "++in_front(${n.treeNext.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++in_front(${n.treeNext.elementType}) -> $expectedIndent" } ctx.localAdj = -1 } private fun adjustExpectedIndentInsideSuperTypeList(n: ASTNode) { expectedIndent++ - debug { "++inside(${n.elementType}) -> $expectedIndent" } + logger.trace { "$line: ++inside(${n.elementType}) -> $expectedIndent" } } private fun adjustExpectedIndentAfterSuperTypeList(n: ASTNode) { @@ -764,7 +755,7 @@ class IndentationRule : Rule( return } expectedIndent-- - debug { "--after(${n.elementType}) -> $expectedIndent" } + logger.trace { "$line: --after(${n.elementType}) -> $expectedIndent" } } private fun adjustExpectedIndentInsideSuperTypeCall(n: ASTNode, ctx: IndentContext) { @@ -774,14 +765,14 @@ class IndentationRule : Rule( } if (n.prevLeaf()?.textContains('\n') == false) { expectedIndent-- - debug { "--inside(${n.elementType}) -> $expectedIndent" } + logger.trace { "$line: --inside(${n.elementType}) -> $expectedIndent" } ctx.exitAdjBy(n, 1) } } private fun adjustExpectedIndentAfterEq(n: ASTNode, ctx: IndentContext) { expectedIndent++ - debug { "++after(EQ) -> $expectedIndent" } + logger.trace { "$line: ++after(EQ) -> $expectedIndent" } ctx.exitAdjBy(n.treeParent, -1) } @@ -791,7 +782,7 @@ class IndentationRule : Rule( val prevBlockLine = ctx.blockOpeningLineStack.peek() ?: -1 if (prevBlockLine != line) { expectedIndent++ - debug { "++after(ARROW) -> $expectedIndent" } + logger.trace { "$line: ++after(ARROW) -> $expectedIndent" } ctx.exitAdjBy(n.treeParent, -1) } } @@ -801,19 +792,19 @@ class IndentationRule : Rule( when { n.isPartOf(FUN) -> { expectedIndent++ - debug { "++after(COLON IN FUN) -> $expectedIndent" } + logger.trace { "$line: ++after(COLON IN FUN) -> $expectedIndent" } val returnType = n.nextCodeSibling() ctx.exitAdjBy(returnType!!, -1) } n.treeParent.isPartOf(SECONDARY_CONSTRUCTOR) -> { expectedIndent++ - debug { "++after(COLON IN CONSTRUCTOR) -> $expectedIndent" } + logger.trace { "$line: ++after(COLON IN CONSTRUCTOR) -> $expectedIndent" } val nextCodeSibling = n.nextCodeSibling() ctx.exitAdjBy(nextCodeSibling!!, -1) } else -> { expectedIndent++ - debug { "++after(COLON) -> $expectedIndent" } + logger.trace { "$line: ++after(COLON) -> $expectedIndent" } ctx.exitAdjBy(n.treeParent, -1) } } @@ -821,7 +812,7 @@ class IndentationRule : Rule( private fun adjustExpectedIndentAfterLparInsideCondition(n: ASTNode, ctx: IndentContext) { expectedIndent++ - debug { "++inside(CONDITION) -> $expectedIndent" } + logger.trace { "$line: ++inside(CONDITION) -> $expectedIndent" } ctx.exitAdjBy(n.treeParent, -1) } @@ -847,7 +838,7 @@ class IndentationRule : Rule( if (arrowNode != null && hasWhiteSpaceWithNewLine) { expectedIndent++ - debug { "++after(FUNCTION_LITERAL) -> $expectedIndent" } + logger.trace { "$line: ++after(FUNCTION_LITERAL) -> $expectedIndent" } ctx.exitAdjBy(arrowNode.prevCodeSibling()!!, -1) } } @@ -1040,7 +1031,7 @@ class IndentationRule : Rule( 0 } else { expectedIndent++ - debug { "++whitespace followed by BY keyword -> $expectedIndent" } + logger.trace { "$line: ++whitespace followed by BY keyword -> $expectedIndent" } 1 } } @@ -1115,9 +1106,8 @@ class IndentationRule : Rule( "Unexpected indentation (${normalizedNodeIndent.length}) (should be $expectedIndentLength)", true ) - debug { - (if (!autoCorrect) "would have " else "") + - "changed indentation to $expectedIndentLength (from ${normalizedNodeIndent.length})" + logger.trace { + "$line: " + (if (!autoCorrect) "would have " else "") + "changed indentation to $expectedIndentLength (from ${normalizedNodeIndent.length})" } } if (autoCorrect) { diff --git a/ktlint-ruleset-test/src/main/kotlin/com/pinterest/ruleset/test/DumpASTRule.kt b/ktlint-ruleset-test/src/main/kotlin/com/pinterest/ruleset/test/DumpASTRule.kt index 4c3f14baec..ea8ed99c28 100644 --- a/ktlint-ruleset-test/src/main/kotlin/com/pinterest/ruleset/test/DumpASTRule.kt +++ b/ktlint-ruleset-test/src/main/kotlin/com/pinterest/ruleset/test/DumpASTRule.kt @@ -30,9 +30,10 @@ public class DumpASTRule @JvmOverloads constructor( emit: (offset: Int, errorMessage: String, corrected: Boolean) -> Unit ) { if (node.isRoot()) { - lineNumberColumnLength = - (node.lastChildLeafOrSelf().lineNumber() ?: 1) - .let { var v = it; var c = 0; while (v > 0) { c++; v /= 10 }; c } + lineNumberColumnLength = node + .lastChildLeafOrSelf() + .lineNumberOrUnknown() + .length lastNode = node.lastChildLeafOrSelf() } var level = -1 @@ -44,11 +45,9 @@ public class DumpASTRule @JvmOverloads constructor( out.println( ( - node.lineNumber() - ?.let { String.format("%${lineNumberColumnLength}s: ", it).dim() } - // should only happen when autoCorrect=true and other rules mutate AST - // in a way that changes text length - ?: String.format("%${lineNumberColumnLength}s: ", "?").dim() + node + .lineNumberOrUnknown() + .let { String.format("%${lineNumberColumnLength}s: ", it).dim() } ) + " ".repeat(level).dim() + colorClassName(node.psi.className) + @@ -61,6 +60,12 @@ public class DumpASTRule @JvmOverloads constructor( " ".repeat(lineNumberColumnLength) + " format: () \"\"".dim() ) + if (node.lineNumberOrUnknown() == "Unknown") { + out.println( + " ".repeat(lineNumberColumnLength) + + " line_number 'Unknown' is caused by mutations in the AST during formatting".dim() + ) + } out.println( " ".repeat(lineNumberColumnLength) + " legend: ~ = org.jetbrains.kotlin, c.i.p = com.intellij.psi".dim() @@ -69,6 +74,17 @@ public class DumpASTRule @JvmOverloads constructor( } } + private fun ASTNode.lineNumberOrUnknown(): String { + val lineNumber = try { + lineNumber().toString() + } catch (e: IndexOutOfBoundsException) { + // Due to autocorrect mutations in the AST it can happen that the node's offset becomes invalid. As a result + // the line number can not be determined. + null + } + return lineNumber ?: "Unknown" + } + private fun elementTypeClassName(elementType: IElementType): String { var name = elementType.toString().substringAfterLast(".").toUpperCase() if (name == "FLOAT_CONSTANT" && elementType == KtTokens.FLOAT_LITERAL) { diff --git a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/DumpAST.kt b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/DumpAST.kt index 0341370863..cba80f43ef 100644 --- a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/DumpAST.kt +++ b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/DumpAST.kt @@ -1,10 +1,5 @@ package com.pinterest.ktlint.test -public val debugAST: () -> Boolean = { - (System.getProperty("ktlintDebug") ?: System.getenv("KTLINT_DEBUG") ?: "") - .toLowerCase().split(",").contains("ast") -} - @Deprecated( message = "Moved to 'test' rulesets. This typealias will be removed in the future versions." ) diff --git a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt index 84d9a7ef2a..a448410597 100644 --- a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt +++ b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt @@ -1,12 +1,48 @@ package com.pinterest.ktlint.test +import com.pinterest.ktlint.core.KTLINT_UNIT_TEST_DUMP_AST +import com.pinterest.ktlint.core.KTLINT_UNIT_TEST_ON_PROPERTY import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.RuleSet +import com.pinterest.ktlint.core.initKtLintKLogger +import com.pinterest.ruleset.test.DumpASTRule +import mu.KotlinLogging import org.assertj.core.api.Assertions import org.assertj.core.util.diff.DiffUtils.diff import org.assertj.core.util.diff.DiffUtils.generateUnifiedDiff +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue + +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + +private fun List.toRuleSets(): List { + val dumpAstRuleSet = System + .getenv(KTLINT_UNIT_TEST_DUMP_AST) + .orEmpty() + .equals(KTLINT_UNIT_TEST_ON_PROPERTY, ignoreCase = true) + .ifTrue { + logger.info { "Dump AST of code before processing as System environment variable $KTLINT_UNIT_TEST_DUMP_AST is set to 'on'" } + RuleSet( + "debug", + DumpASTRule( + // Write to STDOUT. The focus in a failed unit test should first go to the error in the rule that is + // to be tested and not to the AST, + out = System.out + ) + ) + } + return listOfNotNull( + dumpAstRuleSet, + RuleSet( + // RuleSet id is always set to "standard" as this has the side effect that the ruleset id will + // be excluded from the ruleId in the LintError which makes the unit tests of the experimental + // rules easier to maintain as they will not contain the reference to the ruleset id. + "standard", + *toTypedArray() + ) + ) +} public fun Rule.lint( text: String, @@ -43,30 +79,14 @@ public fun List.lint( script: Boolean = false ): List { val res = ArrayList() - val debug = debugAST() - val rules = this.toTypedArray() KtLint.lint( KtLint.Params( fileName = lintedFilePath, text = text, - ruleSets = (if (debug) listOf(RuleSet("debug", DumpAST())) else emptyList()) + - listOf( - RuleSet( - // RuleSet id is always set to "standard" as this has the side effect that the ruleset id will - // be excluded from the ruleId in the LintError which makes the unit tests of the experimental - // rules easier to maintain as they will not contain the reference to the ruleset id. - "standard", - *rules - ) - ), + ruleSets = this.toRuleSets(), userData = userData, script = script, - cb = { e, _ -> - if (debug) { - System.err.println("^^ lint error") - } - res.add(e) - } + cb = { e, _ -> res.add(e) } ) ) return res @@ -114,8 +134,7 @@ public fun List.format( KtLint.Params( fileName = lintedFilePath, text = text, - ruleSets = (if (debugAST()) listOf(RuleSet("debug", DumpAST())) else emptyList()) + - listOf(RuleSet("standard", *rules)), + ruleSets = this.toRuleSets(), userData = userData, script = script, cb = cb diff --git a/ktlint-test/src/main/resources/logback-test.xml b/ktlint-test/src/main/resources/logback-test.xml new file mode 100644 index 0000000000..39df24dbd0 --- /dev/null +++ b/ktlint-test/src/main/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + + + [%level] %logger{36} - %msg%n + + + + + + + diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt index 6d925e7b5b..eed3d09e3d 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt @@ -4,6 +4,7 @@ package com.pinterest.ktlint import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.core.LogLevel import com.pinterest.ktlint.core.ParseException import com.pinterest.ktlint.core.Reporter import com.pinterest.ktlint.core.ReporterProvider @@ -11,9 +12,11 @@ import com.pinterest.ktlint.core.RuleExecutionException import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider import com.pinterest.ktlint.core.VisitorProvider +import com.pinterest.ktlint.core.initKtLintKLogger import com.pinterest.ktlint.core.internal.containsLintError import com.pinterest.ktlint.core.internal.loadBaseline import com.pinterest.ktlint.core.internal.relativeRoute +import com.pinterest.ktlint.core.logLevel import com.pinterest.ktlint.internal.ApplyToIDEAGloballySubCommand import com.pinterest.ktlint.internal.ApplyToIDEAProjectSubCommand import com.pinterest.ktlint.internal.GenerateEditorConfigSubCommand @@ -36,8 +39,6 @@ import java.io.PrintStream import java.net.URLClassLoader import java.net.URLDecoder import java.nio.file.FileSystems -import java.util.ArrayList -import java.util.LinkedHashMap import java.util.ServiceLoader import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.Callable @@ -48,11 +49,14 @@ import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread import kotlin.system.exitProcess +import mu.KotlinLogging import picocli.CommandLine import picocli.CommandLine.Command import picocli.CommandLine.Option import picocli.CommandLine.Parameters +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + fun main(args: Array) { val ktlintCommand = KtlintCommandLine() val commandLine = CommandLine(ktlintCommand) @@ -154,6 +158,12 @@ class KtlintCommandLine { ) var debug: Boolean = false + @Option( + names = ["--trace"], + description = ["Turn on trace output"] + ) + var trace: Boolean = false + @Option( names = ["--disabled_rules"], description = [ @@ -240,6 +250,12 @@ class KtlintCommandLine { private val errorNumber = AtomicInteger() fun run() { + logLevel = when { + trace -> LogLevel.TRACE + debug -> LogLevel.DEBUG + else -> LogLevel.INFO + } + failOnOldRulesetProviderUsage() val start = System.currentTimeMillis() @@ -272,13 +288,7 @@ class KtlintCommandLine { ) } reporter.afterAll() - if (debug) { - System.err.println( - "[DEBUG] ${ - System.currentTimeMillis() - start - }ms / $fileNumber file(s) / $errorNumber error(s)" - ) - } + logger.debug { "${System.currentTimeMillis() - start}ms / $fileNumber file(s) / $errorNumber error(s)" } if (tripped.get()) { exitProcess(1) } @@ -337,9 +347,13 @@ class KtlintCommandLine { @Suppress("Deprecation") private fun failOnOldRulesetProviderUsage() { if (ServiceLoader.load(com.github.shyiko.ktlint.core.RuleSetProvider::class.java).any()) { - System.err.println("[ERROR] Cannot load custom ruleset!") - System.err.println("[ERROR] RuleSetProvider has moved to com.pinterest.ktlint.core.") - System.err.println("[ERROR] Please rename META-INF/services/com.github.shyiko.ktlint.core.RuleSetProvider to META-INF/services/com.pinterest.ktlint.core.RuleSetProvider") + logger.error { + """ + Cannot load custom ruleset!") + RuleSetProvider has moved to com.pinterest.ktlint.core.") + Please rename META-INF/services/com.github.shyiko.ktlint.core.RuleSetProvider to META-INF/services/com.pinterest.ktlint.core.RuleSetProvider") + """.trimIndent() + } exitProcess(1) } } @@ -372,9 +386,9 @@ class KtlintCommandLine { ruleSets: Iterable, visitorProvider: VisitorProvider ): List { - if (debug) { + logger.trace { val fileLocation = if (fileName != KtLint.STDIN_FILE) File(fileName).location(relative) else fileName - System.err.println("[DEBUG] Checking $fileLocation") + "Checking $fileLocation" } val result = ArrayList() if (format) { @@ -456,18 +470,16 @@ class KtlintCommandLine { ): Reporter { val reporterProvider = reporterProviderById[id] if (reporterProvider == null) { - System.err.println( - "Error: reporter \"$id\" wasn't found (available: ${ + logger.error { + "reporter \"$id\" wasn't found (available: ${ reporterProviderById.keys.sorted().joinToString(",") })" - ) + } exitProcess(1) } - if (debug) { - System.err.println( - "[DEBUG] Initializing \"$id\" reporter with $config" + - (output?.let { ", output=$it" } ?: "") - ) + logger.debug { + "Initializing \"$id\" reporter with $config" + + (output?.let { ", output=$it" } ?: "") } val stream = if (output != null) { File(output).parentFile?.mkdirsOrFail(); PrintStream(output, "UTF-8") @@ -481,7 +493,9 @@ class KtlintCommandLine { stream.close() if (tripped.get()) { val outputLocation = File(output).absoluteFile.location(relative) - System.err.println("\"$id\" report written to $outputLocation") + logger.info { + "\"$id\" report written to $outputLocation" + } } } } @@ -501,10 +515,7 @@ class KtlintCommandLine { "Not a valid Kotlin file (${e.message?.toLowerCase()})" ) is RuleExecutionException -> { - if (debug) { - System.err.println("[DEBUG] Internal Error (${e.ruleId})") - e.printStackTrace(System.err) - } + logger.debug("Internal Error (${e.ruleId})", e) LintError( e.line, e.col, @@ -606,12 +617,8 @@ class KtlintCommandLine { URLClassLoader(externalReportersJarPaths.toFilesURIList().toTypedArray()) ) .associateBy { it.id } - .also { - if (debug) { - it.forEach { entry -> - println("[DEBUG] Discovered reporter with \"${entry.key}\" id.") - } - } + .onEach { entry -> + logger.debug { "Discovered reporter with \"${entry.key}\" id." } } private data class LintErrorWithCorrectionInfo( diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/ApplyToIDEACommandHelper.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/ApplyToIDEACommandHelper.kt index 554f873e0f..59e9d389be 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/ApplyToIDEACommandHelper.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/ApplyToIDEACommandHelper.kt @@ -1,8 +1,12 @@ package com.pinterest.ktlint.internal +import com.pinterest.ktlint.core.initKtLintKLogger import java.nio.file.Path import java.nio.file.Paths import kotlin.system.exitProcess +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {}.initKtLintKLogger() class ApplyToIDEACommandHelper( private val applyToProject: Boolean, @@ -14,7 +18,7 @@ class ApplyToIDEACommandHelper( val workDir = Paths.get(".") if (!forceApply && !getUserAcceptanceToUpdateFiles(workDir)) { - println("Update canceled.") + logger.error { "Update canceled." } exitProcess(1) } @@ -25,17 +29,17 @@ class ApplyToIDEACommandHelper( applyToProject ) } catch (e: IntellijIDEAIntegration.ProjectNotFoundException) { - println(".idea directory not found. Are you sure you are inside project root directory?") + logger.error { ".idea directory not found. Are you sure you are inside project root directory?" } exitProcess(1) } - println( + logger.info { """ |Updated. |Please restart your IDE. |If you experience any issues please report them at https://github.com/pinterest/ktlint/issues. """.trimMargin() - ) + } } private fun getUserAcceptanceToUpdateFiles(workDir: Path): Boolean { @@ -45,7 +49,7 @@ class ApplyToIDEACommandHelper( isAndroidCodeStyle, applyToProject ) - println( + logger.info { """ |The following files are going to be updated: |${fileList.joinToString(prefix = "\t", separator = "\n\t")} @@ -53,7 +57,7 @@ class ApplyToIDEACommandHelper( |Do you wish to proceed? [y/n] |(in future, use -y flag if you wish to skip confirmation) """.trimMargin() - ) + } val userInput = generateSequence { readLine() } .filter { it.trim().isNotBlank() } diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/FileUtils.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/FileUtils.kt index ec2b809dc2..8560270273 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/FileUtils.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/FileUtils.kt @@ -5,6 +5,7 @@ import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.VisitorProvider import com.pinterest.ktlint.core.api.FeatureInAlphaState +import com.pinterest.ktlint.core.initKtLintKLogger import java.io.File import java.nio.file.FileSystem import java.nio.file.FileVisitResult @@ -15,6 +16,9 @@ import java.nio.file.Paths import java.nio.file.SimpleFileVisitor import java.nio.file.attribute.BasicFileAttributes import kotlin.system.exitProcess +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {}.initKtLintKLogger() internal val workDir: String = File(".").canonicalPath private val tildeRegex = Regex("^(!)?~") @@ -143,7 +147,7 @@ internal typealias JarFiles = List internal fun JarFiles.toFilesURIList() = map { val jarFile = File(expandTilde(it)) if (!jarFile.exists()) { - println("Error: $it does not exist") + logger.error { "File $it does not exist" } exitProcess(1) } jarFile.toURI().toURL() diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt index ffad41a479..8993fa687f 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt @@ -3,8 +3,12 @@ package com.pinterest.ktlint.internal import com.pinterest.ktlint.KtlintCommandLine import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.api.FeatureInAlphaState +import com.pinterest.ktlint.core.initKtLintKLogger +import mu.KotlinLogging import picocli.CommandLine +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + @CommandLine.Command( description = [ "EXPERIMENTAL!!! Generate kotlin style section for '.editorconfig' file.", @@ -45,14 +49,11 @@ class GenerateEditorConfigSubCommand : Runnable { ) if (generatedEditorConfig.isNotBlank()) { - println( - """ - [*.{kt,kts}] - $generatedEditorConfig - """.trimIndent() - ) + // Do not print to logging on purpose. Output below is intended to be copied to ".editofconfig". Users + // should not be confused with logging markers. + println("[*.{kt,kts}]\n$generatedEditorConfig") } else { - println("Nothing to add to .editorconfig file") + logger.info { "Nothing to add to .editorconfig file" } } } diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GitHookInstaller.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GitHookInstaller.kt index a7f2e2e911..7e038a71ab 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GitHookInstaller.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GitHookInstaller.kt @@ -1,8 +1,12 @@ package com.pinterest.ktlint.internal +import com.pinterest.ktlint.core.initKtLintKLogger import java.io.File import java.io.IOException import kotlin.system.exitProcess +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {}.initKtLintKLogger() object GitHookInstaller { fun installGitHook( @@ -12,7 +16,7 @@ object GitHookInstaller { val gitHooksDir = try { resolveGitHooksDir() } catch (e: IOException) { - System.err.println(e.message) + logger.error { e.message } exitProcess(1) } @@ -25,12 +29,12 @@ object GitHookInstaller { gitHookFile.writeBytes(hookContent) gitHookFile.setExecutable(true) - println( + logger.info { """ .git/hooks/$gitHookName is installed. Be aware that this hook assumes to find ktlint on the PATH. Either ensure that ktlint is actually added to the path or expand the ktlint command in the hook with the path. """.trimIndent() - ) + } } @Throws(IOException::class) @@ -72,7 +76,7 @@ object GitHookInstaller { !actualHookContent.contentEquals(expectedHookContent) ) { val backupFile = hooksDir.resolve("$gitHookName.ktlint-backup.${actualHookContent.hex}") - println(".git/hooks/$gitHookName -> $backupFile") + logger.info { ".git/hooks/$gitHookName -> $backupFile" } hookFile.copyTo(backupFile, overwrite = true) } } diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/PrintASTSubCommand.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/PrintASTSubCommand.kt index e59130172b..39f7138d04 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/PrintASTSubCommand.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/PrintASTSubCommand.kt @@ -5,11 +5,15 @@ import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.ParseException import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.VisitorProvider +import com.pinterest.ktlint.core.initKtLintKLogger import com.pinterest.ruleset.test.DumpASTRule import java.io.File import java.nio.file.FileSystems +import mu.KotlinLogging import picocli.CommandLine +private val logger = KotlinLogging.logger {}.initKtLintKLogger() + @CommandLine.Command( description = [ "Print AST (useful when writing/debugging rules)", @@ -67,13 +71,12 @@ internal class PrintASTSubCommand : Runnable { fileName: String, fileContent: String ) { - if (ktlintCommand.debug) { - val fileLocation = if (fileName != KtLint.STDIN_FILE) { + logger.debug { + "Analyzing " + if (fileName != KtLint.STDIN_FILE) { File(fileName).location(ktlintCommand.relative) } else { "stdin" } - println("[DEBUG] Analyzing $fileLocation") } try { diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/RuleSetsLoader.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/RuleSetsLoader.kt index c7d9797bda..0db0f9ef81 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/RuleSetsLoader.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/RuleSetsLoader.kt @@ -1,10 +1,14 @@ package com.pinterest.ktlint.internal import com.pinterest.ktlint.core.RuleSetProvider +import com.pinterest.ktlint.core.initKtLintKLogger import java.net.URL import java.net.URLClassLoader import java.util.ServiceLoader import java.util.SortedMap +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {}.initKtLintKLogger() /** * Load given list of paths to ruleset jars into map of ruleset providers. @@ -44,7 +48,7 @@ private fun getRuleSetProvidersByUrl( debug: Boolean ): Pair> { if (url != null && debug) { - println("[DEBUG] JAR ruleset provided with path \"${url.path}\"") + logger.debug { "JAR ruleset provided with path \"${url.path}\"" } } val ruleSetProviders = ServiceLoader.load( RuleSetProvider::class.java, @@ -63,16 +67,16 @@ private fun reportWhenMissingCustomRuleSetProvider( .filterNot { it.get().id == "experimental" } .any() if (!hasCustomRuleSetProviders) { - System.err.println( + logger.warn { """ - [WARNING] JAR ${url.path}, provided as command line argument, does not contain a custom ruleset provider. - Check following: - - Does the jar contain an implementation of the RuleSetProvider interface? - - Does the jar contain a resource file with name "com.pinterest.ktlint.core.RuleSetProvider"? - - Is the resource file located in directory "src/main/resources/META-INF/services"? - - Does the resource file contain the fully qualified class name of the class implementing the RuleSetProvider interface? + JAR ${url.path}, provided as command line argument, does not contain a custom ruleset provider. + Check following: + - Does the jar contain an implementation of the RuleSetProvider interface? + - Does the jar contain a resource file with name "com.pinterest.ktlint.core.RuleSetProvider"? + - Is the resource file located in directory "src/main/resources/META-INF/services"? + - Does the resource file contain the fully qualified class name of the class implementing the RuleSetProvider interface? """.trimIndent() // ktlint-disable string-template - ) + } } } diff --git a/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt b/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt index b857a7a7fd..6efd0a1d53 100644 --- a/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt +++ b/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt @@ -16,23 +16,29 @@ class RuleSetsLoaderCLITest : BaseCLITest() { listOf("-R $BASE_DIR_PLACEHOLDER/custom-ruleset/ktlint-ruleset-template.jar") ) { assertNormalExitCode() - assertErrorOutputIsEmpty() + + assertThat(normalOutput) + .noneMatch { + it.matches( + Regex(".* WARN .* JAR .* provided as command line argument, does not contain a custom ruleset provider.") + ) + } } } @Test fun `Display warning when the provided custom ruleset does not contains a ruleset provider`() { - val invalidJarFile = "custom-ruleset/ktlint-ruleset-experimental.jar" + val jarWithoutRulesetProvider = "custom-ruleset/ktlint-reporter-html.jar" runKtLintCliProcess( "custom-ruleset", - listOf("-R", "$BASE_DIR_PLACEHOLDER/$invalidJarFile") + listOf("-R", "$BASE_DIR_PLACEHOLDER/$jarWithoutRulesetProvider") ) { assertNormalExitCode() - assertThat(errorOutput) + assertThat(normalOutput) .anyMatch { it.matches( - Regex("\\[WARNING] JAR .*$invalidJarFile, provided as command line argument, does not contain a custom ruleset provider.") + Regex(".* WARN .* JAR .*$jarWithoutRulesetProvider, provided as command line argument, does not contain a custom ruleset provider.") ) } } diff --git a/ktlint/src/test/kotlin/com/pinterest/ktlint/SimpleCLITest.kt b/ktlint/src/test/kotlin/com/pinterest/ktlint/SimpleCLITest.kt index c04986caf4..b6d94c2466 100644 --- a/ktlint/src/test/kotlin/com/pinterest/ktlint/SimpleCLITest.kt +++ b/ktlint/src/test/kotlin/com/pinterest/ktlint/SimpleCLITest.kt @@ -49,7 +49,7 @@ class SimpleCLITest : BaseCLITest() { "no-code-style-error" ) { assertNormalExitCode() - // assertErrorOutputIsEmpty() // TODO Re-add once PR #1279 is merged + assertErrorOutputIsEmpty() } } diff --git a/ktlint/src/test/resources/cli/custom-ruleset/ktlint-reporter-html.jar b/ktlint/src/test/resources/cli/custom-ruleset/ktlint-reporter-html.jar new file mode 100644 index 0000000000..852d2a84dd Binary files /dev/null and b/ktlint/src/test/resources/cli/custom-ruleset/ktlint-reporter-html.jar differ