https://kotlinlang.org logo
#ktlint
Title
# ktlint
r

Roberto Leinardi

03/19/2023, 5:45 PM
I would like to use KtLint to format some generated code but I'm having some issue with backticks because the generated code contains some of them. Is it possible to tell KtLint to ignore them?
One example is the following enum:
Copy code
public enum class TransformCategory(
    public val nativeValue: GskTransformCategory,
) {
    UNKNOWN(GSK_TRANSFORM_CATEGORY_UNKNOWN),
    ANY(GSK_TRANSFORM_CATEGORY_ANY),
    `3D`(GSK_TRANSFORM_CATEGORY_3D),
    `2D`(GSK_TRANSFORM_CATEGORY_2D),
    `2D_AFFINE`(GSK_TRANSFORM_CATEGORY_2D_AFFINE),
    `2D_TRANSLATE`(GSK_TRANSFORM_CATEGORY_2D_TRANSLATE),
    IDENTITY(GSK_TRANSFORM_CATEGORY_IDENTITY),
    ;
}
The backticks are there because this code is generating some bindings for a C library. The generation is automated via GObject introspection so we can't easily avoid backticks.
p

Paul Dingemans

03/19/2023, 8:07 PM
It is not possible to ignore backtick generically. But in the case of the enum, following works with ktlint `0.48.x`:
Copy code
@Suppress("ktlint:enum-entry-name-case")
public enum class TransformCategory(
    public val nativeValue: GskTransformCategory,
) {
    UNKNOWN(GSK_TRANSFORM_CATEGORY_UNKNOWN),
    ANY(GSK_TRANSFORM_CATEGORY_ANY),
    `3D`(GSK_TRANSFORM_CATEGORY_3D),
    `2D`(GSK_TRANSFORM_CATEGORY_2D),
    `2D_AFFINE`(GSK_TRANSFORM_CATEGORY_2D_AFFINE),
    `2D_TRANSLATE`(GSK_TRANSFORM_CATEGORY_2D_TRANSLATE),
    IDENTITY(GSK_TRANSFORM_CATEGORY_IDENTITY),
    ;
}
r

Roberto Leinardi

03/19/2023, 8:26 PM
unfortunately the bindings are generating several enums, for now I just disable the rule completely via `.editorconfig`:
Copy code
ktlint_standard_enum-entry-name-case = disabled
p

Paul Dingemans

03/19/2023, 8:44 PM
Or, if possible, generate the suppression statement
e

ephemient

03/19/2023, 9:17 PM
I don't see much reason to be checking the formatting of generated code
r

Roberto Leinardi

03/19/2023, 9:35 PM
I don't want to check for formatting, I want to format the code, because KotlinPoet doesn't even allow to change the max line length and they just say to use a formatter like ktlint to deal with formatting of the code: https://github.com/square/kotlinpoet/issues/1020#issuecomment-737547356
e

ephemient

03/19/2023, 9:40 PM
I just leave the kotlinpoet output as-is, generally
but probably you could just run ktlint directly and ignore errors. I think the default callback doesn't do anything with them
Copy code
val ruleProviders = buildSet {
    ServiceLoader.load(RuleSetProviderV2::class.java).flatMapTo(this) { it.getRuleProviders() }
}
val ktLintRuleEngine = KtLintRuleEngine(ruleProviders = ruleProviders)
for (file in files) {
    file.writeText(ktLintRuleEngine.format(file.toPath()))
}
r

Roberto Leinardi

03/19/2023, 9:47 PM
some generated code looks strange without formatting
but after running ktlint it looks much better:
p

Paul Dingemans

03/20/2023, 4:33 PM
If you invoke the
ktlintRuleEngine
to format the generated code, then you can disable the rule via the editorConfigOverride parameter instead of disabling the rule in the
.editorconfig
of the project. The advantage would be that the rule is kept enabled for non-generated code.
r

Roberto Leinardi

03/20/2023, 5:27 PM
This seems very interesting, but I'm not familiar with
ktlintRuleEngine
. Is there some documentation that I can look at? The best would be a link to some open source project that is already doing it.
p

Paul Dingemans

03/20/2023, 5:38 PM
The Ktlint project has lots of unit tests that you can refer to 😉 But I think that this test case exactly matches with what you need https://github.com/pinterest/ktlint/blob/0.48.1-prep/ktlint-core/src/test/kotlin/com/pinterest/ktlint/core/DisabledRulesTest.kt#L100. This is based on the Ktlint
0.48.2
branch. This is refactored in the upcoming release, but that won’t be difficult to change.
r

Roberto Leinardi

03/20/2023, 5:45 PM
Thanks!!
stupid question: which artifact is exposing
KtLintRuleEngine
? I've tried with these but still can't find it:
Copy code
ktlint = { module = "com.pinterest:ktlint", version.ref = "ktlint" }
ktlint-core = { module = "com.pinterest:ktlint-core", version.ref = "ktlint" }
ktlint-api-consumer = { module = "com.pinterest:ktlint-api-consumer", version.ref = "ktlint" }
ktlint-ruleset-standard = { module = "com.pinterest:ktlint-ruleset-standard", version.ref = "ktlint" }
p

Paul Dingemans

03/20/2023, 7:05 PM
Depends a bit against which ktlint version you are developing. There is a kind of big restructuring happening in current master.
r

Roberto Leinardi

03/20/2023, 7:07 PM
I'm trying with the latest stable (I think):
0.48.2
r

Roberto Leinardi

03/20/2023, 7:13 PM
oh ok sorry, I didn't noticed that for core, the group ID is different than from ktlint:
com.pinterest.ktlint:ktlint-core
vs
com.pinterest:ktlint
p

Paul Dingemans

03/20/2023, 7:14 PM
hmm, that was not intended …
e

ephemient

03/20/2023, 7:17 PM
p

Paul Dingemans

03/20/2023, 7:19 PM
In that case I will not change it. But it feels a bit unexpected to me.
It might be related to this remark:
Copy code
<!-- keeping original (pre 0.2.0) qualifier (com.github.shyiko:ktlint) so that no one would notice a thing -->
    <groupId>com.github.shyiko</groupId>
    <artifactId>ktlint</artifactId>
in https://central.sonatype.com/artifact/com.github.shyiko/ktlint/0.31.0
r

Roberto Leinardi

03/20/2023, 7:35 PM
Hey, it works well! I can now format the file immediately after having generated them, without the need to involve Gradle 👍 There is only one issue left: is it possible to remove all the console output when I format? I get spammed with this logs for every single file it scans and I generate hundreds if not thousands of them:
Copy code
20:32:51.072 [main] DEBUG com.pinterest.ktlint.core.internal.RuleExecutionContext - Editor config properties for file '/home/leinardi/Workspace/gitlab/gtk-kn/bindings/core/gio/src/nativeMain/kotlin/bindings/gio/ThemedIcon.kt': {charset=charset = utf-8, indent_style=indent_style = space, insert_final_newline=insert_final_newline = true, max_line_length=max_line_length = 120, trim_trailing_whitespace=trim_trailing_whitespace = true, ij_continuation_indent_size=ij_continuation_indent_size = 4, ij_formatter_off_tag=ij_formatter_off_tag = @formatter:off, ij_formatter_on_tag=ij_formatter_on_tag = @formatter:on, ij_formatter_tags_enabled=ij_formatter_tags_enabled = true, ij_smart_tabs=ij_smart_tabs = false, ij_visual_guides=ij_visual_guides = 80, 100, 120, ij_wrap_on_typing=ij_wrap_on_typing = false, indent_size=indent_size = 4, tab_width=tab_width = 4, ktlint_standard_enum-entry-name-case=ktlint_standard_enum-entry-name-case = disabled, ij_kotlin_align_in_columns_case_branch=ij_kotlin_align_in_columns_case_branch = false, ij_kotlin_align_multiline_binary_operation=ij_kotlin_align_multiline_binary_operation = false, ij_kotlin_align_multiline_extends_list=ij_kotlin_align_multiline_extends_list = false, ij_kotlin_align_multiline_method_parentheses=ij_kotlin_align_multiline_method_parentheses = false, ij_kotlin_align_multiline_parameters=ij_kotlin_align_multiline_parameters = false, ij_kotlin_align_multiline_parameters_in_calls=ij_kotlin_align_multiline_parameters_in_calls = false, ij_kotlin_allow_trailing_comma=ij_kotlin_allow_trailing_comma = true, 
[...]
p

Paul Dingemans

03/20/2023, 7:40 PM
I think that should be possible but I am not sure about how to do that exactly when invoking the API directly. Ktlint exposes following extension function which impact the logger:
Copy code
/**
 * Set the [defaultLoggerModifier]. Note that it can only be set once. It should be set before the first invocation to
 * [initKtLintKLogger].
 */
public fun KLogger.setDefaultLoggerModifier(loggerModifier: (KLogger) -> Unit): KLogger {
and
Copy code
/**
 * Initializes the logger with the [defaultLoggerModifier].
 */
public fun KLogger.initKtLintKLogger(): KLogger {
Another thing that you might try is playing around with configuration of logback. In the unit tests of ktlint this configuration is used to affect logging: https://github.com/pinterest/ktlint/blob/0.48.2/ktlint-test-logging/src/main/resources/logback-test.xml
r

Roberto Leinardi

03/20/2023, 8:03 PM
unfortunately I have a clash of dependencies: I'm using the version 4 of
kotlin-logger
and I'm using
log4j2
instead of
logback
...
even if I try to disable the ktlint logs like this:
Copy code
KotlinLogging
                .logger {}
                .setDefaultLoggerModifier { logger ->
                    (logger.underlyingLogger as Logger).level = Level.OFF
                }
it still messes with the logs of the rest of the code
is there any chance to get in the future an artifacts that doesn't log when used directly as dependency?
p

Paul Dingemans

03/20/2023, 8:08 PM
Sure, as long as if somebody can help me to figure out how to achieve that without breaking the logging functionality for other consumers
I have to go now…
r

Roberto Leinardi

03/20/2023, 8:08 PM
btw, I saw that you are using logback because it allows to change the log level at runtime. I manage to achieve the same with log4j2:
Copy code
import io.github.oshai.KLogger
import io.github.oshai.KotlinLogging
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.appender.ConsoleAppender
import org.apache.logging.log4j.core.config.Configurator
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory

val logger: KLogger by lazy { KotlinLogging.logger("gir") }

private const val LOG4J_PATTERN = "%highlight{%-5level: [%c] %msg%n%throwable}" +
    "{FATAL=bright_red, ERROR=bright_red, WARN=bright_yellow, INFO=bright_green, DEBUG=bright_white, TRACE=white}"

fun configureLog4j(level: io.github.oshai.Level) {
    val builder = ConfigurationBuilderFactory.newConfigurationBuilder()
    val console = builder
        .newAppender("Stdout", "CONSOLE")
        .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT)
    console.add(builder.newLayout("PatternLayout").addAttribute("pattern", LOG4J_PATTERN))
    builder.add(console)
    builder.add(builder.newRootLogger(convertToLog4jLevel(level)).add(builder.newAppenderRef("Stdout")))
    Configurator.initialize(builder.build())
}

private fun convertToLog4jLevel(level: io.github.oshai.Level) = when (level) {
    io.github.oshai.Level.TRACE -> Level.TRACE
    io.github.oshai.Level.DEBUG -> Level.DEBUG
    <http://io.github.oshai.Level.INFO|io.github.oshai.Level.INFO> -> <http://Level.INFO|Level.INFO>
    io.github.oshai.Level.WARN -> Level.WARN
    io.github.oshai.Level.ERROR -> Level.ERROR
}
e

ephemient

03/20/2023, 8:14 PM
I'd expect that you can edit the configuration to target only logs from ktlint classes
Copy code
<logger name="com.pinterest.ktlint" level="NONE" />
or something like that
r

Roberto Leinardi

03/20/2023, 8:27 PM
I'm not familiar with logback, where should I put this xml config? will it be loaded automatically by logback?
r

Roberto Leinardi

03/20/2023, 8:31 PM
ok, I put it in
resources/logback.xml
and it works in the way that I don't see logs from ktlint. But the dependency clash still prevent my logs to work:
Copy code
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jetbrains.kotlin.com.intellij.util.ReflectionUtil (file:/home/leinardi/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-compiler-embeddable/1.8.0/eb9118d4bcceaa2a94b2ae2a33a4ddba7c9a947f/kotlin-compiler-embeddable-1.8.0.jar) to field java.lang.Throwable.backtrace
WARNING: Please consider reporting this to the maintainers of org.jetbrains.kotlin.com.intellij.util.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Any plan to migrate to
kotlin-logger
v4?
p

Paul Dingemans

03/21/2023, 8:32 AM
The warnings above are related to using java 16+. You need to open-up some libraries to make them disappear. There is no specific reason for not upgrading the kotlin-logging dependency to v4 afaik. I will investigate if I can make it easier for API consumers to set the log level. Most likely, a parameter can be added to the ktlintRuleEngine to set the log-level.
r

Roberto Leinardi

03/21/2023, 10:40 AM
Hey thanks, I found this issue with more info about the warnings: https://github.com/pinterest/ktlint/issues/1618 Unfortunately all the workaround I tried didn't work for me, maybe because I'm on JDK11? Anyway, I have good news about the logs: if I avoid importing
com.pinterest:ktlint
and instead I import only
ktlint-core
and the
ktlint-ruleset-*
I do not have any issues with the logs: ktlint doesn't log anything and log4j2 still works for me! Do you think there are some side effects in doing this (not importing
com.pinterest:ktlint
but only core and rulesets)?
p

Paul Dingemans

03/21/2023, 10:42 AM
I don’t expect any side effect. The root of the project only contains the build logic for all sub modules.
Would you be willing to write a kind of how-to-guide about what you tried to achieve and how you solved it? It would be nice to add this to the Ktlint documentation.
r

Roberto Leinardi

03/21/2023, 10:44 AM
sure! Is it ok if I do it in this ticket I opened yesterday? https://github.com/pinterest/ktlint/issues/1875
p

Paul Dingemans

03/21/2023, 10:47 AM
Preferably as a PR on the Ktlint project in the markdown format we use. For example see https://raw.githubusercontent.com/pinterest/ktlint/master/docs/faq.md
r

Roberto Leinardi

03/21/2023, 11:20 AM
OK, I'll try to do it this evening 👍
512 Views