Skip to content

Commit

Permalink
Merge branch 'main' into trailing_comments
Browse files Browse the repository at this point in the history
  • Loading branch information
nreid260 authored Sep 15, 2023
2 parents 6c3174a + b1d2c1b commit b0d8b14
Show file tree
Hide file tree
Showing 14 changed files with 715 additions and 443 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ For comparison, the same code formatted by [`ktlint`](https://github.com/pintere
| ------ | --------|
| ![ktlint](docs/images/ktlint.png) | ![IntelliJ](docs/images/intellij.png) |

## Playground

We have a [live playground](https://facebook.github.io/ktfmt/) 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
Expand Down
6 changes: 3 additions & 3 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
<parent>
<groupId>com.facebook</groupId>
<artifactId>ktfmt-parent</artifactId>
<version>0.44</version>
<version>0.45-SNAPSHOT</version>
</parent>

<properties>
<dokka.version>0.10.1</dokka.version>
<kotlin.version>1.6.10</kotlin.version>
<kotlin.version>1.6.20-M1</kotlin.version>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<main.class>com.facebook.ktfmt.cli.Main</main.class>
Expand Down Expand Up @@ -144,7 +144,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
<version>32.0.0-jre</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
Expand Down
42 changes: 21 additions & 21 deletions core/src/main/java/com/facebook/ktfmt/cli/ParsedArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,38 +38,34 @@ data class ParsedArgs(
) {
companion object {

/** processArgs parses command-line arguments passed to ktfmt. */
fun processArgs(err: PrintStream, args: Array<String>): ParsedArgs {
if (args.size == 1 && args[0].startsWith("@")) {
return parseOptions(err, File(args[0].substring(1)).readLines().toTypedArray())
} else {
return parseOptions(err, args)
}
}

/** parseOptions parses command-line arguments passed to ktfmt. */
fun parseOptions(err: PrintStream, args: Array<String>): ParsedArgs {
val fileNames = mutableListOf<String>()
var formattingOptions = FormattingOptions()
var dryRun = false
var setExitIfChanged = false
var removeUnusedImports = true
var stdinName: String? = null

for (arg in args) {
when {
arg == "--dropbox-style" -> formattingOptions = Formatter.DROPBOX_FORMAT
arg == "--google-style" -> formattingOptions = Formatter.GOOGLE_FORMAT
arg == "--kotlinlang-style" -> formattingOptions = Formatter.KOTLINLANG_FORMAT
arg == "--dry-run" || arg == "-n" -> dryRun = true
arg == "--set-exit-if-changed" -> setExitIfChanged = true
arg == "--do-not-remove-unused-imports" -> removeUnusedImports = false
arg.startsWith("--stdin-name") -> stdinName = parseKeyValueArg(err, "--stdin-name", arg)
arg.startsWith("--") -> err.println("Unexpected option: $arg")
arg.startsWith("@") -> err.println("Unexpected option: $arg")
else -> fileNames.add(arg)
fun parseArgs(args: Array<String>) {
for (arg in args) {
when {
arg == "--dropbox-style" -> formattingOptions = Formatter.DROPBOX_FORMAT
arg == "--google-style" -> formattingOptions = Formatter.GOOGLE_FORMAT
arg == "--kotlinlang-style" -> formattingOptions = Formatter.KOTLINLANG_FORMAT
arg == "--dry-run" || arg == "-n" -> dryRun = true
arg == "--set-exit-if-changed" -> setExitIfChanged = true
arg == "--do-not-remove-unused-imports" -> removeUnusedImports = false
arg.startsWith("--stdin-name") -> stdinName = parseKeyValueArg(err, "--stdin-name", arg)
arg.startsWith("--") -> err.println("Unexpected option: $arg")
arg.startsWith("@") -> parseArgs(readArgsFile(arg))
else -> fileNames.add(arg)
}
}
}

parseArgs(args)

return ParsedArgs(
fileNames,
formattingOptions.copy(removeUnusedImports = removeUnusedImports),
Expand All @@ -87,5 +83,9 @@ data class ParsedArgs(
}
return parts[1]
}

/** Reads an argument file prefixed with "@" */
private fun readArgsFile(arg: String): Array<String> =
File(arg.substring(1)).readLines().toTypedArray()
}
}
151 changes: 107 additions & 44 deletions core/src/main/java/com/facebook/ktfmt/format/KotlinInputAstVisitor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtConstructorDelegationCall
import org.jetbrains.kotlin.psi.KtContainerNode
import org.jetbrains.kotlin.psi.KtContextReceiverList
import org.jetbrains.kotlin.psi.KtContinueExpression
import org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
Expand Down Expand Up @@ -95,6 +96,7 @@ import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.psi.KtScriptInitializer
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
Expand Down Expand Up @@ -166,15 +168,16 @@ class KotlinInputAstVisitor(
builder.sync(function)
builder.block(ZERO) {
visitFunctionLikeExpression(
modifierList = function.modifierList,
keyword = "fun",
typeParameters = function.typeParameterList,
receiverTypeReference = function.receiverTypeReference,
name = function.nameIdentifier?.text,
parameterList = function.valueParameterList,
typeConstraintList = function.typeConstraintList,
bodyExpression = function.bodyBlockExpression ?: function.bodyExpression,
typeOrDelegationCall = function.typeReference,
contextReceiverList = function.getStubOrPsiChild(KtStubElementTypes.CONTEXT_RECEIVER_LIST),
modifierList = function.modifierList,
keyword = "fun",
typeParameters = function.typeParameterList,
receiverTypeReference = function.receiverTypeReference,
name = function.nameIdentifier?.text,
parameterList = function.valueParameterList,
typeConstraintList = function.typeConstraintList,
bodyExpression = function.bodyBlockExpression ?: function.bodyExpression,
typeOrDelegationCall = function.typeReference,
)
}
}
Expand Down Expand Up @@ -285,6 +288,7 @@ class KotlinInputAstVisitor(
* list of supertypes.
*/
private fun visitFunctionLikeExpression(
contextReceiverList: KtContextReceiverList?,
modifierList: KtModifierList?,
keyword: String?,
typeParameters: KtTypeParameterList?,
Expand All @@ -308,6 +312,9 @@ class KotlinInputAstVisitor(
}

builder.block(ZERO) {
if (contextReceiverList != null) {
visitContextReceiverList(contextReceiverList)
}
if (modifierList != null) {
visitModifierList(modifierList)
}
Expand Down Expand Up @@ -1218,20 +1225,31 @@ class KotlinInputAstVisitor(
val leftMostExpression = parts.first()
visit(leftMostExpression.left)
for (leftExpression in parts) {
when (leftExpression.operationToken) {
KtTokens.RANGE -> {}
KtTokens.ELVIS -> builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent)
else -> builder.space()
}
builder.token(leftExpression.operationReference.text)
val isFirst = leftExpression === leftMostExpression
if (isFirst) {
builder.open(expressionBreakIndent)
}

when (leftExpression.operationToken) {
KtTokens.RANGE -> {}
KtTokens.ELVIS -> builder.space()
else -> builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO)
KtTokens.RANGE -> {
if (isFirst) {
builder.open(expressionBreakIndent)
}
builder.token(leftExpression.operationReference.text)
}
KtTokens.ELVIS -> {
if (isFirst) {
builder.open(expressionBreakIndent)
}
builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO)
builder.token(leftExpression.operationReference.text)
builder.space()
}
else -> {
builder.space()
if (isFirst) {
builder.open(expressionBreakIndent)
}
builder.token(leftExpression.operationReference.text)
builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO)
}
}
visit(leftExpression.right)
}
Expand Down Expand Up @@ -1391,6 +1409,7 @@ class KotlinInputAstVisitor(

builder.block(ZERO) {
visitFunctionLikeExpression(
contextReceiverList = null,
modifierList = accessor.modifierList,
keyword = accessor.namePlaceholder.text,
typeParameters = null,
Expand All @@ -1400,6 +1419,7 @@ class KotlinInputAstVisitor(
typeConstraintList = null,
bodyExpression = accessor.bodyBlockExpression ?: accessor.bodyExpression,
typeOrDelegationCall = accessor.returnTypeReference,

)
}
}
Expand Down Expand Up @@ -1458,16 +1478,24 @@ class KotlinInputAstVisitor(
if (expression.getPrevSiblingIgnoringWhitespace() is PsiComment) {
return false // Leading comments cause weird indentation.
}
if (expression is KtLambdaExpression) {
return true

var carry = expression
if (carry is KtCallExpression) {
if (carry.valueArgumentList?.leftParenthesis == null &&
carry.lambdaArguments.isNotEmpty() &&
carry.typeArgumentList?.arguments.isNullOrEmpty()) {
carry = carry.lambdaArguments[0].getArgumentExpression()
} else {
return false
}
}
if (carry is KtLabeledExpression) {
carry = carry.baseExpression
}
if (expression is KtCallExpression &&
expression.valueArgumentList?.leftParenthesis == null &&
expression.lambdaArguments.isNotEmpty() &&
expression.typeArgumentList?.arguments.isNullOrEmpty() &&
expression.lambdaArguments.first().getArgumentExpression() is KtLambdaExpression) {
if (carry is KtLambdaExpression) {
return true
}

return false
}

Expand All @@ -1476,24 +1504,33 @@ class KotlinInputAstVisitor(
val breakToExpr = genSym()
builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent, Optional.of(breakToExpr))

val lambdaExpression =
when (expr) {
is KtLambdaExpression -> expr
is KtCallExpression -> {
visit(expr.calleeExpression)
builder.space()
expr.lambdaArguments[0].getLambdaExpression() ?: fail()
}
else -> throw AssertionError(expr)
}
var carry = expr
if (carry is KtCallExpression) {
visit(carry.calleeExpression)
builder.space()
carry = carry.lambdaArguments[0].getArgumentExpression()
}
if (carry is KtLabeledExpression) {
visit(carry.labelQualifier)
carry = carry.baseExpression ?: fail()
}
if (carry is KtLambdaExpression) {
visitLambdaExpressionInternal(carry, brokeBeforeBrace = breakToExpr)
return
}

visitLambdaExpressionInternal(lambdaExpression, brokeBeforeBrace = breakToExpr)
throw AssertionError(carry)
}

override fun visitClassOrObject(classOrObject: KtClassOrObject) {
builder.sync(classOrObject)
val contextReceiverList =
classOrObject.getStubOrPsiChild(KtStubElementTypes.CONTEXT_RECEIVER_LIST)
val modifierList = classOrObject.modifierList
builder.block(ZERO) {
if (contextReceiverList != null) {
visitContextReceiverList(contextReceiverList)
}
if (modifierList != null) {
visitModifierList(modifierList)
}
Expand Down Expand Up @@ -1559,6 +1596,7 @@ class KotlinInputAstVisitor(

val delegationCall = constructor.getDelegationCall()
visitFunctionLikeExpression(
contextReceiverList = constructor.getStubOrPsiChild(KtStubElementTypes.CONTEXT_RECEIVER_LIST),
modifierList = constructor.modifierList,
keyword = "constructor",
typeParameters = null,
Expand Down Expand Up @@ -1663,6 +1701,19 @@ class KotlinInputAstVisitor(
builder.forcedBreak()
}

/** Example `context(Logger, Raise<Error>)` */
override fun visitContextReceiverList(contextReceiverList: KtContextReceiverList) {
builder.sync(contextReceiverList)
builder.token("context")
visitEachCommaSeparated(
contextReceiverList.contextReceivers(),
prefix = "(",
postfix = ")",
breakAfterPrefix = false,
breakBeforePostfix = false)
builder.forcedBreak()
}

/** For example `@Magic private final` */
override fun visitModifierList(list: KtModifierList) {
builder.sync(list)
Expand Down Expand Up @@ -2423,16 +2474,22 @@ class KotlinInputAstVisitor(

override fun visitKtFile(file: KtFile) {
markForPartialFormat()
var importListEmpty = false
val importListEmpty = file.importList?.text?.isBlank() ?: true

var isFirst = true
for (child in file.children) {
if (child.text.isBlank()) {
importListEmpty = child is KtImportList
continue
}
if (!isFirst && child !is PsiComment && (child !is KtScript || !importListEmpty)) {
builder.blankLineWanted(OpsBuilder.BlankLineWanted.YES)
}

builder.blankLineWanted(
when {
isFirst -> OpsBuilder.BlankLineWanted.NO
child is PsiComment -> OpsBuilder.BlankLineWanted.NO
child is KtScript && importListEmpty -> OpsBuilder.BlankLineWanted.PRESERVE
else -> OpsBuilder.BlankLineWanted.YES
})

visit(child)
isFirst = false
}
Expand All @@ -2442,6 +2499,7 @@ class KotlinInputAstVisitor(
override fun visitScript(script: KtScript) {
markForPartialFormat()
var lastChildHadBlankLineBefore = false
var lastChildIsContextReceiver = false
var first = true
for (child in script.blockExpression.children) {
if (child.text.isBlank()) {
Expand All @@ -2451,13 +2509,18 @@ class KotlinInputAstVisitor(
val childGetsBlankLineBefore = child !is KtProperty
if (first) {
builder.blankLineWanted(OpsBuilder.BlankLineWanted.PRESERVE)
} else if (lastChildIsContextReceiver) {
builder.blankLineWanted(OpsBuilder.BlankLineWanted.NO)
} else if (child !is PsiComment &&
(childGetsBlankLineBefore || lastChildHadBlankLineBefore)) {
builder.blankLineWanted(OpsBuilder.BlankLineWanted.YES)
}
visit(child)
builder.guessToken(";")
lastChildHadBlankLineBefore = childGetsBlankLineBefore
lastChildIsContextReceiver =
child is KtScriptInitializer &&
child.firstChild?.firstChild?.firstChild?.text == "context"
first = false
}
markForPartialFormat()
Expand Down
Loading

0 comments on commit b0d8b14

Please sign in to comment.