Repository: scalameta/scalameta (scala)

Issue:
Properly formatted code using zio method `*>` is formatted so the code is no longer valid
## Configuration (required) ##

```
version = "3.8.0"

runner.dialect = scala3

fileOverride {
  "glob:**/project/*" {
    runner.dialect = scala213
  }
  "glob:**/jooq-generated/src/test/scala/**" {
    runner.dialect = scala213
  }
}

maxColumn = 150
lineEndings = unix
indent.defnSite = 2
assumeStandardLibraryStripMargin = true
align.stripMargin = true
newlines.beforeCurlyLambdaParams = multilineWithCaseOnly
rewrite.scala3.removeOptionalBraces = true
rewrite.rules = [AvoidInfix]
rewrite.neverInfix.excludeFilters = [
  until,
  to,
  by,
  eq,
  ne,
  "should.*",
  "contain.*",
  "must.*",
  in,
  ignore,
  be,
  taggedAs,
  thrownBy,
  synchronized,
  have,
  when,
  size,
  only,
  noneOf,
  oneElementOf,
  noElementsOf,
  atLeastOneElementOf,
  atMostOneElementOf,
  allElementsOf,
  inOrderElementsOf,
  theSameElementsAs,
  theSameElementsInOrderAs,
  at,
  inSet,
  ilike,
  between,
  ifNull
]

# excludes any generated code (e.g., jOOQ) from formatting
project.excludePaths = [
  "glob:**/src/**/generated/**"
]

onTestFailure = "To fix this, run 'sbt fix' from the project root directory."

```


## Command-line parameters (required) ##

When I run scalafmt via CLI like this: `scalafmt -c ./.scalafmt.conf Test.scala`


## Steps

Given code like this:
```scala
import zio.*

object Test:
  ZIO
    .succeed(42)
    .map: i =>
      i + 1
    *> ZIO.succeed(666)
```

## Problem

Scalafmt formats code like this:
```scala
import zio.*

object Test:
  ZIO
    .succeed(42)
    .map: i =>
      i + 1
        *> ZIO.succeed(666)
```

## Expectation

I would like the formatted output to look like this:
```scala
import zio.*

object Test:
  ZIO
    .succeed(42)
    .map: i =>
      i + 1
    *> ZIO.succeed(666)
```

Pull request:
Fixes scalameta/scalafmt#3825.

Patch:
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
index e60e5c1b5e..ff1e5797b7 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
@@ -911,6 +911,16 @@ final class ScannerTokens(val tokens: Tokens)(implicit dialect: Dialect) {
             if (prevToken.is[Indentation]) _ => None
             else getOutdentIfNeeded(_).fold(getIndentIfNeeded, x => Some(Right(x)))
 
+          def maybeWithLF(regions: List[SepRegion]) = {
+            val res = getIfCanProduceLF(regions, nextIndent)
+            if (res.isEmpty) sepRegionsOrig match {
+              case (ri: SepRegionIndented) :: _ if ri.indent < nextIndent =>
+                Some(Left(RegionLine(nextIndent) :: sepRegionsOrig))
+              case _ => res
+            }
+            else res
+          }
+
           @tailrec
           def iter(regions: List[SepRegion]): Option[Either[List[SepRegion], TokenRef]] = {
             val res = regionsToRes(regions).orElse {
@@ -927,12 +937,12 @@ final class ScannerTokens(val tokens: Tokens)(implicit dialect: Dialect) {
                     if (next.is[Dot]) None
                     else
                       rc.asBody() match {
-                        case Some(body) => getIfCanProduceLF(body :: rs, nextIndent)
+                        case Some(body) => maybeWithLF(body :: rs)
                         case None => iter(rs)
                       }
                   case _ => iter(rs)
                 }
-              case rs => getIfCanProduceLF(rs, nextIndent)
+              case rs => maybeWithLF(rs)
             }
             else res
           }


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InfixSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InfixSuite.scala
index 3dac9c0880..2622e3a564 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InfixSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InfixSuite.scala
@@ -653,4 +653,107 @@ class InfixSuite extends BaseDottySuite {
     runTestAssert[Stat](code, Some(layout))(tree)
   }
 
+  test("scalafmt #3825 1") {
+    val code =
+      """|object a:
+         |  foo
+         |    .map: i =>
+         |      i + 1
+         |    *> bar
+         |""".stripMargin
+    val layout =
+      """|object a {
+         |  foo.map {
+         |    i => i + 1
+         |  } *> bar
+         |}
+         |""".stripMargin
+    val tree = Defn.Object(
+      Nil,
+      tname("a"),
+      tpl(
+        Term.ApplyInfix(
+          Term.Apply(
+            Term.Select(tname("foo"), tname("map")),
+            blk(
+              Term.Function(
+                List(tparam("i")),
+                Term.ApplyInfix(tname("i"), tname("+"), Nil, List(int(1)))
+              )
+            ) :: Nil
+          ),
+          tname("*>"),
+          Nil,
+          List(tname("bar"))
+        )
+      )
+    )
+    runTestAssert[Stat](code, layout)(tree)
+  }
+
+  test("scalafmt #3825 2") {
+    val code =
+      """|object a:
+         |  object b:
+         |    foo
+         |      .map: i =>
+         |        i + 1
+         |          + 2
+         |        + 3
+         |      *> bar
+         |    baz
+         |  qux
+         |""".stripMargin
+    val layout =
+      """|object a {
+         |  object b {
+         |    foo.map {
+         |      i => i + 1 + 2 + 3
+         |    } *> bar
+         |    baz
+         |  }
+         |  qux
+         |}
+         |""".stripMargin
+    val tree = Defn.Object(
+      Nil,
+      tname("a"),
+      tpl(
+        Defn.Object(
+          Nil,
+          tname("b"),
+          tpl(
+            Term.ApplyInfix(
+              Term.Apply(
+                Term.Select(tname("foo"), tname("map")),
+                blk(
+                  Term.Function(
+                    List(tparam("i")),
+                    Term.ApplyInfix(
+                      Term.ApplyInfix(
+                        Term.ApplyInfix(tname("i"), tname("+"), Nil, List(int(1))),
+                        tname("+"),
+                        Nil,
+                        List(int(2))
+                      ),
+                      tname("+"),
+                      Nil,
+                      List(int(3))
+                    )
+                  )
+                ) :: Nil
+              ),
+              tname("*>"),
+              Nil,
+              List(tname("bar"))
+            ),
+            tname("baz")
+          )
+        ),
+        tname("qux")
+      )
+    )
+    runTestAssert[Stat](code, layout)(tree)
+  }
+
 }