GithubHelp home page GithubHelp logo

facebook / ktfmt Goto Github PK

View Code? Open in Web Editor NEW
769.0 13.0 58.0 5.78 MB

A program that reformats Kotlin source code to comply with the common community standard for Kotlin code conventions.

Home Page: https://facebook.github.io/ktfmt/

License: Apache License 2.0

Kotlin 91.81% Shell 0.13% Java 4.02% CSS 1.36% JavaScript 1.34% HTML 1.34%

ktfmt's Introduction

ktfmt

ktfmt is a program that pretty-prints (formats) Kotlin code, based on google-java-format.

Note that ktfmt still has some rough edges which we're constantly working on fixing.

The minimum supported runtime version is JDK 11, released September 2018.

Demo

Before Formatting Formatted by ktfmt
Original ktfmt

For comparison, the same code formatted by ktlint and IntelliJ:

Formatted by ktlint Formatted by IntelliJ
ktlint IntelliJ

Playground

We have a live playground where you can easily see how ktfmt would format your code. Give it a try! https://facebook.github.io/ktfmt/

Using the formatter

IntelliJ, Android Studio, and other JetBrains IDEs

A ktfmt IntelliJ plugin is available from the plugin repository. To install it, go to your IDE's settings and select the Plugins category. Click the Marketplace tab, search for the ktfmt plugin, and click the Install button.

The plugin will be disabled by default. To enable it in the current project, go to File→Settings...→ktfmt Settings (or IntelliJ IDEA→Preferences...→Editor→ktfmt Settings on macOS) and check the Enable ktfmt checkbox. (A notification will be presented when you first open a project offering to do this for you.)

To enable it by default in new projects, use File→New Project Settings→Preferences for new Projects→Editor→ktfmt Settings.

When enabled, it will replace the normal Reformat Code action, which can be triggered from the Code menu or with the Ctrl-Alt-L (by default) keyboard shortcut.

To configure IntelliJ to approximate ktfmt's formatting rules during code editing, you can edit your project's .editorconfig file to include the Kotlin section from one of the files inside docs/editorconfig. Not all of ktfmt's rules can be represented as IntelliJ editor settings, so you will still need to run ktfmt. Alternately, that file can be used as a reference to manually change the project's code style settings.

from the command-line

Download the formatter and run it with:

java -jar /path/to/ktfmt-<VERSION>-jar-with-dependencies.jar [--dropbox-style] [files...]

--dropbox-style makes ktfmt use a block indent of 4 spaces instead of 2. See below for details.

Note: There is no configurability as to the formatter's algorithm for formatting (apart from --dropbox-style). This is a deliberate design decision to unify our code formatting on a single format.

using Gradle

A Gradle plugin (ktfmt-gradle) is available on the Gradle Plugin Portal. To set it up, just follow the instructions in the How-to-use section.

Alternatively, you can use Spotless with the ktfmt Gradle plugin.

using Maven

Consider using Spotless with the ktfmt Maven plugin.

FAQ

ktfmt vs ktlint vs IntelliJ

ktfmt uses google-java-format's underlying engine, and as such, many items on google-java-format's FAQ apply to ktfmt as well.

In particular,

  1. ktfmt ignores most existing formatting. It respects existing newlines in some places, but in general, its output is deterministic and is independent of the input code.
  2. ktfmt exposes no configuration options that govern formatting behavior. See https://github.com/google/google-java-format/wiki/FAQ#i-just-need-to-configure-it-a-bit-differently-how for the rationale.

These two properties make ktfmt a good fit in large Kotlin code bases, where consistency is very important.

We created ktfmt because ktlint and IntelliJ sometime fail to produce nice-looking code that fits in 100 columns, as can be seen in the Demo section.

ktfmt uses a 2-space indent; why not 4? any way to change that?

Two reasons -

  1. Many of our projects use a mixture of Kotlin and Java, and we found the back-and-forth in styles to be distracting.
  2. From a pragmatic standpoint, the formatting engine behind google-java-format uses more whitespace and newlines than other formatters. Using an indentation of 4 spaces quickly reaches the maximal column width.

However, we do offer an escape-hatch for projects that absolutely cannot make the move to ktfmt because of 2-space: the --dropbox-style flag changes block indents to 4-space.

Developer's Guide

Setup

  • Open pom.xml in IntelliJ. Choose "Open as a Project"
  • The IntelliJ project will unfortunately be broken on import. To fix,
    • Turn off ErrorProne by removing the compiler parameters in IntelliJ at the bottom of "Settings -> Build, Execution, Deployment -> Compiler -> Java Compiler" (see google/google-java-format#417)

Development

  • Currently, we mainly develop by adding tests to FormatterTest.kt.

Building on the Command Line

  • Run mvn install
  • Run java -jar core/target/ktfmt-<VERSION>-jar-with-dependencies.jar

Releasing

See RELEASING.md.

License

Apache License 2.0

ktfmt's People

Contributors

avarun42 avatar bddckr avatar bethcutler avatar cgrushko avatar cloudshiftchris avatar cortinico avatar davidtorosyan avatar debrechtabel avatar dependabot[bot] avatar distributedlock avatar dnmolchanov avatar facebook-github-bot avatar franvis avatar hick209 avatar javiersegoviacordoba avatar jef avatar jxddk avatar mpetrov avatar nedtwigg avatar nreid260 avatar salvatorebenedetto avatar sgrimm avatar steventrouble avatar strulovich avatar tnorbye avatar xiphirx avatar zertosh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ktfmt's Issues

One parameter per line if function prototype doesn't fit entirely in one line

private fun someMethod(
      metaData: JsonNode,
      interfaceIdex: Int,
      registryIdex: Int
  ): ArrayNode? {

For this one, it should be the right format, but after I use ktfmt, it changes to

private fun getMethodsOrderedImplementationsWithMethods(
      metaData: JsonNode, interfaceIdex: Int, registryIdex: Int
  ): ArrayNode? {

Free feel to talk to me directly via workchat wyl@fb

IllegalStateException on Java 11 JVM

When ktfmt is run on a Java 11 JVM (AdoptOpenJDK 11.0.5+10) it bombs out at startup:

wax~/work/ktfmt % java -jar ~/work/ktfmt/core/target/ktfmt-0.1-SNAPSHOT-jar-with-dependencies.jar /tmp/x.kt                                                                             
Exception in thread "main" java.lang.IllegalStateException: LOGGING: Loading modules: [java.se, jdk.accessibility, jdk.attach, jdk.compiler, jdk.dynalink, jdk.httpserver, jdk.jartool, jdk.javadoc, jdk.jconsole, jdk.jdi, jdk.jfr, jdk.jshell, jdk.jsobject, jdk.management, jdk.management.jfr, jdk.net, jdk.scripting.nashorn, jdk.sctp, jdk.security.auth, jdk.security.jgss, jdk.unsupported, jdk.unsupported.desktop, jdk.xml.dom, java.base, java.compiler, java.datatransfer, java.desktop, java.xml, java.instrument, java.logging, java.management, java.management.rmi, java.rmi, java.naming, java.net.http, java.prefs, java.scripting, java.security.jgss, java.security.sasl, java.sql, java.transaction.xa, java.sql.rowset, java.xml.crypto, jdk.internal.jvmstat, jdk.management.agent, jdk.jdwp.agent, jdk.internal.ed, jdk.internal.le, jdk.internal.opt] (no MessageCollector configured)
	at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.report(ClasspathRootsResolver.kt:312)
	at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.report$default(ClasspathRootsResolver.kt:310)
	at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.addModularRoots(ClasspathRootsResolver.kt:253)
	at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.computeRoots(ClasspathRootsResolver.kt:123)
	at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.convertClasspathRoots(ClasspathRootsResolver.kt:79)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:229)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:125)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.createForProduction(KotlinCoreEnvironment.kt:432)
	at com.facebook.ktfmt.Parser.parse(Parser.kt:33)
	at com.facebook.ktfmt.FormatterKt.format(Formatter.kt:28)
	at com.facebook.ktfmt.FormatterKt.format$default(Formatter.kt:27)
	at com.facebook.ktfmt.MainKt.main(Main.kt:29)

It runs successfully on a Java 8 JVM.

Spotless and 0.19

With 0.18 I get it working, but with 0.19 I get this exception:

java.lang.ClassNotFoundException: com.facebook.ktfmt.FormattingOptions$Companion

Return type moved to its own line if signature too long

The Kotlin style guide says function signatures that don't fit on one line should be formatted like

fun longMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType
): ReturnType {
  // body
}

But in cases where the argument list fits on one line but the return type pushes it over the limit, ktfmt renders this as

fun longMethodName(argument: ArgumentType = defaultValue, argument2: AnotherArgumentType):
    ReturnType {
  // body
}

Space after link at end of sentence in KDoc

The handling of links in the middle of sentences is much better now, but the new behavior introduced one quirk.

/**
 * This is a comment that links to [AnotherClass].
 */

gets formatted as

/**
 * This is a comment that links to [AnotherClass] .
 */

Continuation indents in KDoc

Continuation indents in KDoc block tags are currently removed. Is this a specific design decision?

/**
 * Summary fragment.
 * 
 * @param blockTag This is the first
 * block tag.
 */

If the block tag wraps onto a new line, ktfmt will not use any indentation, and remove 1- or 2-space indentation that already exists. Indents of 3 or more spaces will be automatically maintained with ``` marks.

The Google Style Guide for Java recommends at least 4 spaces indent (consistent with the continuation indent). I would propose that ktfmt likewise use the continuation indent here.

Should continuation indentation be 2 or 4 spaces?

i.e., should code look like this: (2 spaces)

fun f() {
  val x =
    43
  aVeryLongFunctionName(
    a, b, c, d, e)
}

or like this? (4 spaces)

fun f() {
  val x =
      43
  aVeryLongFunctionName(
      a, b, c, d, e)
}

In favor of 2 spaces:

  1. That's what IntelliJ's code conventions define. We don't know the reasons. It could be that it's just simpler this way.

In favor of 4 spaces:

  1. It's looks more like Java.
  2. Given ktfmt, it's as easy to format with 4 as it is with 2.

Markdown lists not preserved in KDoc comments

ktfmt does not recognize Markdown list syntax in KDoc comments, and concatenates them into a single paragraph. Both numbered and bulleted lists are affected.

Input:

/**
 * This function does a couple things.
 *
 * 1. Demonstrates Markdown list formatting. 
 * 2. Demonstrates ktfmt reformatting.
 */ 
fun commentFormatTest() {}

ktfmt output:

/**
 * This function does a couple things.
 *
 * 1. Demonstrates Markdown list formatting. 2. Demonstrates ktfmt reformatting.
 */
fun commentFormatTest() {}

Consider using GJF 1.7

I've given ktmt a spin in the Uber monorepo. Due to a number of issues, Android is still locked into JDK8, so we're capped at GJF 1.7 until the JDK 11 migration finishes. If ktfmt isn't using the new APIs in 1.8, I'd consider taking a slightly earlier version to enable a broader range of support on jdk 8.

I'm sure we're not the only potential users with issues.

Expressions inlined with imports can be lost

If we try to format this:

import com.example.Sample
import com.example.zab // test
import com.example.foo ; val x = Sample(foo, zab)

The result will be:

import com.example.Sample
import com.example.foo
import com.example.zab // test val x = Sample(foo, zab)

As you can see, the expression became a comment.

Primary constructor formatting

For primary constructors ktfmt currently format this code

A

class MyView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    @AttrRes defaultAttr = ...,
    @StyleRes defaultStyle = ...,
) : View(context, attrs, defaultAttr, defaultStyle) {
  ...

into this

B

class MyView 
    @JvmOverloads
    constructor(
        context: Context,
        attrs: AttributeSet? = null,
        @AttrRes defaultAttr = ...,
        @StyleRes defaultStyle = ...,
    ) : View(context, attrs, defaultAttr, defaultStyle) {
  ...

It does not seem like A would violate the rectangle rule since, but in case it does, I believe I would prefer something more similar to this

C

class MyView 
@JvmOverloads
constructor(
    context: Context,
    attrs: AttributeSet? = null,
    @AttrRes defaultAttr = ...,
    @StyleRes defaultStyle = ...,
) : View(context, attrs, defaultAttr, defaultStyle) {
  ...

since it gets closer to what we have defined in the guidelines.

This is something that seems like it's still being debated (Kotlin/kotlin-style-guide#2) so there are no clear guidelines on what we should do here, still I say my personal preference is A > C >> B.

Introduce proper KDoc parsing and Formatting

The first version of Ktfmt uses the java-doc formatter from Google-Java-Format with very few forks to make it palatable. However, this will keep us stumbling onto issues. We need instead to figure a clean way to parse KDoc and output them instead.
@cgrushko pointed out that kotlinc can parse that, so this may be the first step to figuring out a good solution.

Closing parentheses placement does not match Kotlin style guide

I'm not sure if this is on purpose or merely a side effect of the google-java-format-based implementation, but ktfmt's placement of closing parentheses on expressions or argument lists that it splits across multiple lines doesn't match the placement in the official Kotlin style guide, which says there should be a line break. It seems to be a general rule there, but backed by some specific examples:

Obviously ktfmt is under no obligation to strictly follow that style guide; if that's on purpose please close this and I'll avoid filing any similar issues in the future.

Fails to format file with annotations with use-site targets

Hi there!

I've looked into using this tool to format our codebase since we are really not that happy with KtLint because of the issues you mentioned in the README already.

I noticed however that if a class has a property with an annotation that has a use-site target ktfmt errors.

So the class looks like this:

class Something {
	@field:[Inject Named("WEB_VIEW")]
    internal lateinit var httpClient: OkHttpClient
}

The error is the following one:

error: expected token: '@'; generated internal instead
com.google.googlejavaformat.FormattingError: 71:5: error: expected token: '@'; generated internal instead

	at com.google.googlejavaformat.OpsBuilder.token(OpsBuilder.java:315)
	at com.facebook.ktfmt.KotlinInputAstVisitor.token(KotlinInputAstVisitor.kt:1689)
	at com.facebook.ktfmt.KotlinInputAstVisitor.token$default(KotlinInputAstVisitor.kt:1688)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitKeywordModifiers(KotlinInputAstVisitor.kt:1115)
	at com.facebook.ktfmt.KotlinInputAstVisitor.declareOne(KotlinInputAstVisitor.kt:785)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitProperty(KotlinInputAstVisitor.kt:354)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitProperty(KtVisitorVoid.java:489)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitProperty(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtProperty.accept(KtProperty.java:58)
	at org.jetbrains.kotlin.psi.KtElementImplStub.accept(KtElementImplStub.java:59)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitStatements(KotlinInputAstVisitor.kt:347)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitBlockBody(KotlinInputAstVisitor.kt:328)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitClassOrObject(KotlinInputAstVisitor.kt:886)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClassOrObject(KtVisitorVoid.java:465)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClassOrObject(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtVisitor.visitClass(KtVisitor.java:33)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClass(KtVisitorVoid.java:33)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClass(KtVisitorVoid.java:459)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClass(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtClass.accept(KtClass.kt:20)
	at org.jetbrains.kotlin.psi.KtElementImplStub.accept(KtElementImplStub.java:59)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitKtFile(KotlinInputAstVisitor.kt:1660)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitKtFile(KtVisitorVoid.java:513)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitKtFile(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtFile.accept(KtFile.kt:242)
	at org.jetbrains.kotlin.psi.KtFile.accept(KtFile.kt:229)
	at com.facebook.ktfmt.FormatterKt.format(Formatter.kt:79)
	at com.facebook.ktfmt.Main.formatFile(Main.kt:82)
	at com.facebook.ktfmt.Main.access$formatFile(Main.kt:31)
	at com.facebook.ktfmt.Main$run$1.test(Main.kt:63)
	at com.facebook.ktfmt.Main$run$1.test(Main.kt:31)
	at java.base/java.util.stream.MatchOps$1MatchSink.accept(MatchOps.java:90)
	at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1601)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:127)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:502)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.MatchOps$MatchTask.doLeaf(MatchOps.java:306)
	at java.base/java.util.stream.MatchOps$MatchTask.doLeaf(MatchOps.java:277)
	at java.base/java.util.stream.AbstractShortCircuitTask.compute(AbstractShortCircuitTask.java:115)
	at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)

I would be more than willing to see how I can fix this by making a PR perhaps with some guidance on where to start :).

Reconsider 2 vs 4 space indentation

ktfmt looks like a very promising alternative to ktlint!
However, the 2 space format makes it pretty much impossible for me to introduce ktfmt on any reasonably sized existing codebase. Given that consistent indentation is so important (especially within the same file) I don't think I can even try out ktfmt with the 2 spaces choice (I could live with lots of other migration inconsistencies, but this one would be to glaring),

4 spaces are the absolute standard in any existing Kotlin codebase, so I believe that this decision will really hinder people's ability to use this library.
I would suggest you reconsider this decision in order to unlock wider adoption :)

Support formatting .kts files

If I try to run ktfmt on a .kts file, it doesn't like the fact that the script contains top-level statements. For example:

$ echo 'println("Hello world")' > demo.kts
$ java -jar ktfmt-0.19-jar-with-dependencies.jar demo.kts
demo.kts:1:1: error: Expecting a top level declaration

It'd be nice to be able to auto-format Kotlin scripts, particularly build.gradle.kts.

Publish artifacts to Maven Central or JCenter

It'd be nice to have official jars available in one of the usual places for jar distribution, rather than having to build the code locally. Among other things, being able to specify ktfmt as a dependency would make it easier to build, say, a Gradle or Maven plugin to run it as part of a build process.

Build fails with Java 11 JDK

This seems to be a problem in google-java-format rather than in ktfmt, so the actual issue here is just that it should be documented in the README. Running mvn install with a Java 11 JDK (AdoptOpenJDK 11.0.5+10) fails:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:3.0.0:jar (attach-javadocs) on project google-java-format: Execution attach-javadocs of goal org.apache.maven.plugins:maven-javadoc-plugin:3.0.0:jar failed.: NullPointerException -> [Help 1]

With a Java 8 JDK, it builds successfully.

`ktfmt` chokes on files with CRLF line breaks

I just tried ktfmt on the simplest possible correct Kotlin program:

fun main(){
println("test")
}

And was unpleasantly surprised when it failed:

C:\ayane>java -jar \bin\ktfmt-0.13-jar-with-dependencies.jar main.kt
main.kt:1:12: error: Expecting an element

This is the same version, and indeed copy, of ktfmt that the other day was successfully formatting a much more complex program. Any idea what's wrong here?

Kotlin script files (.kts)

ktfmt looks good so far! Only deficiency I have found is that it doesn't work on script (.kts) files. Any chance of adding this?

Entropy leak: not removing semicolons

Very happy with ktfmt so far! (I would like line width to be 120 columns instead of 100, but I understand the reason for not making this configurable.)

One place where entropy from the input is leaking through is semicolons, which are still present in the output. (I imagine the most likely way for this to arise in practice is the way it arose for me: converting Java code by copy pasting it and letting IDEA convert it automatically, which works pretty well a lot of the time but also doesn't remove the semicolons.) Hopefully this would be easy to do?

Blank lines between global constants

Generally speaking, ktfmt seems to have a policy of putting a blank line between top-level declarations. Which for most things, such as functions and classes, is good!

It's not as good for global constant declarations, e.g. in a parser I'm writing at the moment, the token constants end up looking like this:

private const val EOF = (-1).toChar()

private const val AND = 0.toChar()

private const val ARROW = 1.toChar()

private const val EQUALS = 2.toChar()

private const val GREATER_EQUALS = 3.toChar()

private const val IDENTIFIER = 4.toChar()

private const val INT_LITERAL = 5.toChar()

private const val LESS_EQUALS = 6.toChar()

private const val NOT_EQUALS = 7.toChar()

... etc

which doesn't look so good.

Any chance of removing the blank lines between global constants?

Closing parentheses placement for method calls does not match Kotlin style guide

This came up in #10, but IIUC the changes made in that issue were only for method declarations, not method calls.

See e.g. https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting:

drawSquare(
    x = 10, y = 10,
    width = 100, height = 100,
    fill = true
)

One reason this might be worth reconsidering is the support for trailing commas that was added in Kotlin 1.4 (see KT-9476), which will allow writing:

 drawSquare(
     x = 10, y = 10,
     width = 100, height = 100,
-    fill = true
+    fill = true,
 )

Leaving the closing paren ) on the previous line seems like bad style:

 drawSquare(
     x = 10, y = 10,
     width = 100, height = 100,
     fill = true,)

Breaks inserted into middle of return type

I have an interface with a function whose return value has type arguments, and ktfmt breaks the type arguments across multiple lines. This seems to violate the "prefer to break at a higher semantic level" approach of google-java-format.

Original:

interface X {
  fun functionWithGenericReturnType(
    arg1: Arg1Type, arg2: Arg2Type
  ): Map<String, Map<String, Double>>?
}

ktfmt output:

interface X {
  fun functionWithGenericReturnType(arg1: Arg1Type, arg2: Arg2Type): Map<
      String,
      Map<String, Double>>?
}

I would have expected it to reformat to something like my original formatting, or perhaps put each argument on a separate line, such that Map<String, Map<String, Double>>? would be treated as a single semantic unit and kept on the same line.

Space after KDoc link is removed

Maybe already covered by #15 but ktfmt changes

/**
 * Doc line with a reference to [AnotherClass] in the middle of a sentence
 */

to

/**
 * Doc line with a reference to [AnotherClass]in the middle of a sentence
 */

Lambda param when it doesn't fit in the same lane

If the code with a lambda doesn't fit in a line the lambda parameter is moving down to the next line so:

veryLongBar {
    param ->
    doSomething(param)
}
  1. I think it should add, at least, a break line between param -> and doSomething.
veryLongBar {
    param ->

    doSomething(param)
}
  1. If it can be chained, the chain should have to have the priority
foo
    .bar
    .veryLongBaz { param ->
        doSomething(param)
    }
  1. Another case to consider is when the lambda can fit in one line after the chain.
foo
    .bar
    .baz { param -> doSomething(param) }

A real example of this:

launchFragmentInContainer<SomeFragment>(themeResId = R.style.AppTheme).onFragment {
    fragment ->
    doSomething(fragment )
}

In this case, I think it should be reformated to

launchFragmentInContainer<SomeFragment>(themeResId = R.style.AppTheme)
    .onFragment { fragment -> doSomething(fragment ) }

Sorry for the indentation, I am writing this from mobile and the preview doesn't show the same indent as the markdown 😕

Standalone Gradle Plugin

Thanks for the great tool 👍

I wrote a standalone Gradle plugin for ktfmt: https://github.com/cortinico/ktfmt-gradle as it was missing (Gradle users had to rely necessarily on Spotless).

I'm currently using it for a couple of projects of mine and it works fine for my use case.

I'd love to collect some feedback and share it with the community.
I'm also wondering if we could link it in the README?

Handling call chains starting with a lambda call

We need to make a decision on to handle something such as this:

with(Foo()) {
  doFooStuff()
  doMoreFooStuff()
}.also {
  println("Wow")
  println("Amaze")
}

Generally in call chains, we break before a dot, and in the case of a chain with lambdas code looks like this:

fooFoo
    .callMethod {
      doFooStuff()
      doMoreFooStuff()
    }
    .also {
      println("Wow")
      println("Amaze")
    }

There is an example a . which is a when expression:

when (f) {
  ...
}.exhaustive

A few options we have:

Option 1: dot right after }.

with(Foo()) {
  doFooStuff()
  doMoreFooStuff()
}.also {
  println("Wow")
  println("Amaze")
}

Problem: The chain keeps looking different than other types of chains

Option 2

Extra indentation to make it look like if we had a chained call

with(Foo()) {
      doFooStuff()
      doMoreFooStuff()
    }
    .also {
      println("Wow")
      println("Amaze")
    }

Problem, might be weird to have the extra indentation

Option 3

Floating dot

with(Foo()) {
  doFooStuff()
  doMoreFooStuff()
}
    .also {
      println("Wow")
      println("Amaze")
    }

What option should we pursue? I have code doing option 2.

ktfmt fails on trailing commas

They are already supported by Kotlin 1.3.72 when the language level is set to 1.4.

This is the exception:

com.google.googlejavaformat.FormattingError: 84:59: error: expected token: ','; generated ) instead

	at com.google.googlejavaformat.OpsBuilder.token(OpsBuilder.java:315)
	at com.facebook.ktfmt.KotlinInputAstVisitor.token(KotlinInputAstVisitor.kt:1689)
	at com.facebook.ktfmt.KotlinInputAstVisitor.token$default(KotlinInputAstVisitor.kt:1688)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitPrimaryConstructor(KotlinInputAstVisitor.kt:949)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitPrimaryConstructor(KtVisitorVoid.java:477)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitPrimaryConstructor(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtPrimaryConstructor.accept(KtPrimaryConstructor.kt:32)
	at org.jetbrains.kotlin.psi.KtElementImplStub.accept(KtElementImplStub.java:59)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitClassOrObject(KotlinInputAstVisitor.kt:865)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClassOrObject(KtVisitorVoid.java:465)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClassOrObject(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtVisitor.visitClass(KtVisitor.java:33)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClass(KtVisitorVoid.java:33)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClass(KtVisitorVoid.java:459)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitClass(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtClass.accept(KtClass.kt:20)
	at org.jetbrains.kotlin.psi.KtElementImplStub.accept(KtElementImplStub.java:59)
	at com.facebook.ktfmt.KotlinInputAstVisitor.visitKtFile(KotlinInputAstVisitor.kt:1660)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitKtFile(KtVisitorVoid.java:513)
	at org.jetbrains.kotlin.psi.KtVisitorVoid.visitKtFile(KtVisitorVoid.java:21)
	at org.jetbrains.kotlin.psi.KtFile.accept(KtFile.kt:242)
	at org.jetbrains.kotlin.psi.KtFile.accept(KtFile.kt:229)
	at com.facebook.ktfmt.FormatterKt.format(Formatter.kt:79)
	at com.facebook.ktfmt.Main.formatFile(Main.kt:82)
	at com.facebook.ktfmt.Main.access$formatFile(Main.kt:31)
	at com.facebook.ktfmt.Main$run$1.test(Main.kt:63)
	at com.facebook.ktfmt.Main$run$1.test(Main.kt:31)
	at java.base/java.util.stream.MatchOps$1MatchSink.accept(MatchOps.java:90)
	at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1631)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:127)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:502)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.MatchOps$MatchTask.doLeaf(MatchOps.java:306)
	at java.base/java.util.stream.MatchOps$MatchTask.doLeaf(MatchOps.java:277)
	at java.base/java.util.stream.AbstractShortCircuitTask.compute(AbstractShortCircuitTask.java:115)
	at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)

Qualified expression exceeds line length when there are only 2 parts

The following test method fails:

  @Test
  fun `foo`() = assertFormatted("""
-------------------------------------
fun foo() {
  kjsdfglkjdfgkjdfkgjhkerjghkdfj
      ?.methodName()
}
|""".trimMargin(), deduceMaxWidth = true)

ktfmt formats the body as

fun foo() {
  kjsdfglkjdfgkjdfkgjhkerjghkdfj?.methodName()
}

which exceeds the line length.

The reason is that the "// Maybe break before . or ?." branch is not taken.

Inconsistent indentation of "when" branches

It's possible to get three different indent levels in a single when statement. Ktfmt output:

fun test(x: String) {
  when (x) {
    "foo" ->
        synchronized(this) {
          println("A statement")
          println("Another statement")
        }
    "bar" ->
    // A comment
    println("Okay")
    "baz" -> {
      println("Just a statement")
      println("And another")
    }
  }
}

Canonical .editorconfig

This is kind of the opposite of #45 -- it'd be convenient to have a complete .editorconfig based on the plugin's current formatting rules, including editor-specific config properties for IntelliJ and any other editors people care to contribute.

Obviously this doesn't have to be generated by the plugin; it could be a hand-maintained file in the plugin source.

Break before Elvis operator rather than after

Both the official and Android Kotlin style guides are silent on this topic, but I think breaking before the Elvis (?:) operator if the expression doesn't fit on one line, rather than after it, would improve readability.

Example:

someObject.someMethodReturningCollection()
    .map { it.someProperty }
    .find { it.contains(someSearchValue) }
    ?: someDefaultValue

Currently ktfmt would render this as

someObject.someMethodReturningCollection()
    .map { it.someProperty }
    .find { it.contains(someSearchValue) } ?:
    someDefaultValue

This is of course totally syntactically correct but I think it makes the flow of control a little harder to follow, and is inconsistent with the formatting of ?. which ktfmt puts at the start of the line.

Kotlin 1.4 support

Presumably this will require updates to support new language features like fun interface and trailing commas?

ktfmt fails to parse angle brackets in certain cases

Example 1. In the case of a class inheriting from more than one class or interface, angle bracket syntax is used to denote the supertype from which the inherited implementation is taken.

package com.myandroidapp

import android.app.Application
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner

class App : Application(), DefaultLifecycleObserver {

  override fun onCreate() {
    // com.google.googlejavaformat.FormattingError: 11:15: error: did not generate token "<"
    super<Application>.onCreate()
   // etc.
  }
}

Expected behaviour: ktfmt parses/reformats the source code.
Actual behaviour: results in an error similar to com.google.googlejavaformat.FormattingError: 11:15: error: did not generate token "<"

Update: appears to be fixed in 2c17040

Example 2 Type Parameters in Annotations: for example, the @TypeParceler annotation from Kotlinx's Parcelize library allows you to explicitly specify an implementation of Parcelable for types that do not have built-in serialisation support.

import java.util.UUID

import kotlinx.android.parcel.*

@Parcelize
@TypeParceler<UUID, UUIDParceler>()
data class User(
  val id: UUID
)

Expected behaviour: ktfmt parses/reformats the source code.
Actual behaviour: results in an error similar to com.google.googlejavaformat.FormattingError: 5:21: error: expected token: '('; generated < instead or 5:21: error: expected token: 'data'; generated < instead if the empty parentheses are removed.


Using ktfmt 0.13 with Spotless plugin for Gradle 4.4.0; Gradle 6.5.0. Targeting Kotlin 1.3.72.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.