Skip to content

Commit 426425d

Browse files
Nivaldo Bondançameta-codesync[bot]
authored andcommitted
Treat dot-qualified scoping functions as block-like
Summary: # AI generated `val fn = scope.launch { doThing() }` was reformatted by ktfmt, but `val fn = launch { doThing() }` was not. Both patterns should be stable (idempotent). `isLambdaOrScopingFunction()` only recognized `KtCallExpression` (e.g., `launch { }`), not `KtQualifiedExpression` (e.g., `scope.launch { }`). Added unwrapping for dot-qualified expressions with simple name receivers so `scope.launch { }` and `scope?.launch { }` are treated as block-like, consistent with `launch { }`. Guarded with `receiverExpression is KtSimpleNameExpression` to avoid catching multi-part chains like `items.toMutableList.apply { }` or `if (...) { }.mapFailure { }`. Reviewed By: cgrushko Differential Revision: D96184015 fbshipit-source-id: 36eb699782766e523fb19cddbb8350232a3331cf
1 parent c2678a3 commit 426425d

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1717
- Add `--quiet` flag to suppress per-file formatting status output.
1818

1919
### Fixed
20+
- Dot-qualified scoping functions (e.g., `scope.launch { }`) now format as block-like expressions, consistent with non-qualified calls ([GH#205](https://github.com/facebook/ktfmt/issues/205))
2021
- Backtick-escaped full-path imports are no longer incorrectly removed as unused (https://github.com/facebook/ktfmt/issues/532)
2122
- Single-line comments in if expressions are now correctly indented (https://github.com/facebook/ktfmt/pull/591)
2223
- Idea Plugin not applying custom trailing commas management strategy (https://github.com/facebook/ktfmt/pull/593)

core/src/main/java/com/facebook/ktfmt/format/KotlinInputAstVisitor.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,7 @@ class KotlinInputAstVisitor(
15531553
* 1. '... = { ... }' is a lambda expression
15541554
* 2. '... = Runnable { ... }' is considered a scoping function
15551555
* 3. '... = scope { ... }' '... = apply { ... }' is a scoping function
1556+
* 4. '... = scope.launch { ... }' is a dot-qualified scoping function
15561557
*
15571558
* but not:
15581559
* 1. '... = foo() { ... }' due to the empty parenthesis
@@ -1565,6 +1566,9 @@ class KotlinInputAstVisitor(
15651566
}
15661567

15671568
var carry = expression
1569+
if (carry is KtQualifiedExpression && carry.receiverExpression is KtSimpleNameExpression) {
1570+
carry = carry.selectorExpression
1571+
}
15681572
if (carry is KtCallExpression) {
15691573
if (
15701574
carry.valueArgumentList?.leftParenthesis == null &&
@@ -1592,6 +1596,11 @@ class KotlinInputAstVisitor(
15921596
builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent, Optional.of(breakToExpr))
15931597

15941598
var carry = expr
1599+
if (carry is KtQualifiedExpression && carry.receiverExpression is KtSimpleNameExpression) {
1600+
visit(carry.receiverExpression)
1601+
builder.token(carry.operationSign.value)
1602+
carry = carry.selectorExpression
1603+
}
15951604
if (carry is KtCallExpression) {
15961605
visit(carry.calleeExpression)
15971606
builder.space()

core/src/test/java/com/facebook/ktfmt/format/FormatterTest.kt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6807,6 +6807,84 @@ class FormatterTest {
68076807
deduceMaxWidth = true,
68086808
)
68096809

6810+
@Test
6811+
fun `dot-qualified scoping functions are block-like`() =
6812+
assertFormatted(
6813+
"""
6814+
|/////////////////////////////////
6815+
|fun f() {
6816+
| val fn = scope.launch {
6817+
| doThing()
6818+
| doAnother()
6819+
| }
6820+
|}
6821+
|
6822+
|fun g() {
6823+
| val longVariableName =
6824+
| scope.launch {
6825+
| doThing()
6826+
| doAnother()
6827+
| }
6828+
|}
6829+
|
6830+
|fun h() = scope.launch {
6831+
| doThing()
6832+
| doAnother()
6833+
|}
6834+
|
6835+
|fun longFunctionName() =
6836+
| scope.launch {
6837+
| doThing()
6838+
| doAnother()
6839+
| }
6840+
|
6841+
|fun j() {
6842+
| x = scope.launch {
6843+
| doThing()
6844+
| doAnother()
6845+
| }
6846+
|}
6847+
|
6848+
|fun k() {
6849+
| longVariableName =
6850+
| scope.launch {
6851+
| doThing()
6852+
| doAnother()
6853+
| }
6854+
|}
6855+
|
6856+
|fun l() {
6857+
| val fn = scope?.launch {
6858+
| doThing()
6859+
| doAnother()
6860+
| }
6861+
|}
6862+
|
6863+
|fun m() {
6864+
| val longVariableName =
6865+
| scope?.launch {
6866+
| doThing()
6867+
| doAnother()
6868+
| }
6869+
|}
6870+
|"""
6871+
.trimMargin(),
6872+
deduceMaxWidth = true,
6873+
)
6874+
6875+
@Test
6876+
fun `dot-qualified scoping functions single-line`() =
6877+
assertFormatted(
6878+
"""
6879+
|///////////////////////////////////////////
6880+
|fun f() {
6881+
| val fn = scope.launch { doThing() }
6882+
|}
6883+
|"""
6884+
.trimMargin(),
6885+
deduceMaxWidth = true,
6886+
)
6887+
68106888
@Test
68116889
fun `top level properties with other types preserve newline spacing`() {
68126890
assertFormatted(

0 commit comments

Comments
 (0)