Repository: scalameta/scalameta (scala)

Issue:
Scalafmt inserts the wrong endmarker, creating invalid code (Scala3)
## Configuration (required) ##

Please paste the **smallest** possible set of `.scalafmt.conf`
configuration parameters that reproduces the problem:
```
runner.dialect = "Scala3"
version = "3.8.2"
rewrite.scala3.insertEndMarkerMinLines = 10
```

## Command-line parameters (required) ##

When I run scalafmt via CLI like this: `scalafmt -c .scalafmt.conf`

## Steps

Given code like this:
```scala
class FmtTest:

  private val const = 3

  def testBrokenMatch(s: String) =
    s match
      case "hello" =>
        val a = 1
        val b = 1
        val c = 1
        val d = 1
        val e = 1
        val f = 1
        val g = 1
        val h = 1
        val i = 1
        val j = 1
        val k = 1
        val l = 1
    end match
  
end FmtTest
```

## Problem

Scalafmt formats code like this:
```scala
class FmtTest:

  private val const = 3

  def testBrokenMatch(s: String) =
    s match
      case "hello" =>
        val a = 1
        val b = 1
        val c = 1
        val d = 1
        val e = 1
        val f = 1
        val g = 1
        val h = 1
        val i = 1
        val j = 1
        val k = 1
        val l = 1
    end match

  end FmtTest
end FmtTest
```

The extra FmtTest of course then fails fmt in future calls as it is not valid.

## Expectation

I would like the formatted output to look like this:
```scala
class FmtTest:

  private val const = 3

  def testBrokenMatch(s: String) =
    s match
      case "hello" =>
        val a = 1
        val b = 1
        val c = 1
        val d = 1
        val e = 1
        val f = 1
        val g = 1
        val h = 1
        val i = 1
        val j = 1
        val k = 1
        val l = 1
    end match
  end testBrokenMatch // Or nothing
end FmtTest
```

## Workaround

I've found that by adding the `end testBrokenMatch` marker myself, scalafmt does not add an extra `end FmtTest` anymore.

Additionally, if I don't include include `end match`, scalafmt doesn't alter the code at all.

## Notes

The private val or an import seems required to trigger the formatting threshold of trying to insert the extra marker.

I see a similar situation with simply calling a method and passing a body for which an end marker can't be labeled, such as munit's test:

```scala
class MyTest extends munit.FunSuite:
  import com.github.plokhotnyuk.jsoniter_scala.core.*
  test("some test name"):
    // body here, ending with an `end match`

end MyTest
```

In this case, scalafmt will insert an extra `end MyTest`.

Pull request:
Fixes scalameta/scalafmt#4098.

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 4087554563..7a4035772c 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
@@ -625,7 +625,8 @@ final class ScannerTokens(val tokens: Tokens)(implicit dialect: Dialect) {
       def eolRef(rs: List[SepRegion], out: Token) = TokenRef(rs, out, eolPos, nextPos, eolPos, null)
 
       def lastWhitespaceToken(rs: List[SepRegion], lineIndent: Int) = {
-        val regions = if (lineIndent < 0) rs else RegionLine(lineIndent) :: rs
+        val addRegionLine = lineIndent >= 0 && findIndent(rs) < lineIndent
+        val regions = if (addRegionLine) RegionLine(lineIndent) :: rs else rs
         val token = tokens(eolPos)
         val out =
           if (!multiEOL || token.is[MultiEOL]) token


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala
index a7d0fdf609..4409d6d72b 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala
@@ -311,4 +311,61 @@ class EndMarkerSuite extends BaseDottySuite {
     ))
   }
 
+  test("scalafmt#4098") {
+    val code = """|class FmtTest:
+                  |
+                  |  private val const = 3
+                  |
+                  |  def testBrokenMatch(s: String) =
+                  |    s match
+                  |      case "hello" =>
+                  |        val a = 1
+                  |    end match
+                  |  
+                  |end FmtTest
+                  |""".stripMargin
+    val layout = """|class FmtTest {
+                    |  private val const = 3
+                    |  def testBrokenMatch(s: String) = {
+                    |    s match {
+                    |      case "hello" =>
+                    |        val a = 1
+                    |    }
+                    |    end match
+                    |  }
+                    |}
+                    |end FmtTest
+                    |""".stripMargin
+    val tree = Source(List(
+      Defn.Class(
+        Nil,
+        pname("FmtTest"),
+        Nil,
+        ctor,
+        tpl(
+          Defn.Val(List(Mod.Private(anon)), List(Pat.Var(tname("const"))), None, lit(3)),
+          Defn.Def(
+            Nil,
+            tname("testBrokenMatch"),
+            Nil,
+            List(List(tparam("s", "String"))),
+            None,
+            blk(
+              Term.Match(
+                tname("s"),
+                List(
+                  Case(lit("hello"), None, blk(Defn.Val(Nil, List(Pat.Var(tname("a"))), None, lit(1))))
+                ),
+                Nil
+              ),
+              Term.EndMarker(tname("match"))
+            )
+          )
+        )
+      ),
+      Term.EndMarker(tname("FmtTest"))
+    ))
+    runTestAssert[Source](code, layout)(tree)
+  }
+
 }