Repository: scalameta/scalameta
Pull request #4036 (2024-11-17 09:50:22)

ScalametaParser: enhance expected error


Patch:
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
index 516cf6f07..eb6545e09 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
@@ -408,26 +408,29 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
   private def syntaxErrorExpected[T <: Token: ClassTag]: Nothing = syntaxErrorExpected[T](currToken)
   private def syntaxErrorExpected[T <: Token: ClassTag](tok: Token): Nothing =
     syntaxError(syntaxExpectedMessage[T](tok), at = tok)
-  private def expectAt[T <: Token: ClassTag](tok: Token, msg: => String, exists: Boolean): Unit =
-    if (tok.is[T] != exists) syntaxError(msg, at = tok)
+  private def expectAt[T <: Token: ClassTag](
+      tok: Token,
+      exists: Boolean
+  )(msg: => String, prefix: => String): Unit = if (tok.is[T] != exists)
+    syntaxError(Option(prefix).filter(_.nonEmpty).fold(msg)(p => s"$p: $msg"), at = tok)
 
   @inline
-  private def expectAt[T <: Token: ClassTag](tok: Token): Unit =
-    expectAt[T](tok, syntaxExpectedMessage[T](tok), exists = true)
+  private def expectAt[T <: Token: ClassTag](tok: Token, prefix: => String = ""): Unit =
+    expectAt[T](tok, exists = true)(msg = syntaxExpectedMessage[T](tok), prefix = prefix)
   @inline
   private def expect[T <: Token: ClassTag]: Unit = expectAt[T](currToken)
   @inline
-  private def expect[T <: Token: ClassTag](msg: => String): Unit =
-    expectAt[T](currToken, msg, exists = true)
+  private def expect[T <: Token: ClassTag](prefix: => String): Unit =
+    expectAt[T](currToken, prefix = prefix)
 
   @inline
-  private def expectNotAt[T <: Token: ClassTag](tok: Token): Unit =
-    expectAt[T](tok, syntaxNotExpectedMessage[T], exists = false)
+  private def expectNotAt[T <: Token: ClassTag](tok: Token, prefix: => String = ""): Unit =
+    expectAt[T](tok, exists = false)(msg = syntaxNotExpectedMessage[T], prefix = prefix)
   @inline
   private def expectNot[T <: Token: ClassTag]: Unit = expectNotAt[T](currToken)
   @inline
-  private def expectNot[T <: Token: ClassTag](msg: => String): Unit =
-    expectAt[T](currToken, msg, exists = false)
+  private def expectNot[T <: Token: ClassTag](prefix: => String): Unit =
+    expectNotAt[T](currToken, prefix = prefix)
 
   /** Consume one token of the specified type, or signal an error if it is not there. */
   def accept[T <: Token: ClassTag]: Unit = {
@@ -2427,7 +2430,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
   }
 
   def caseClause(forceSingleExpr: Boolean = false): Case = {
-    expectNot[KwCase]("Unexpected `case`")
+    expectNot[KwCase]
     autoEndPos(prevIndex) {
       def caseBody() = {
         accept[RightArrow]
@@ -3938,7 +3941,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
     if (isAfterOptNewLine[LeftBrace]) templateBodyOnLeftBrace(owner)
     else if (at[Colon] && peekToken.isAny[Indentation, EOL]) autoPos {
       next()
-      expect[Indentation.Indent]("expected template body")
+      expect[Indentation.Indent]("template body")
       templateBodyOnIndentRaw(owner)
     }
     else if (at[LeftParen])
@@ -4290,8 +4293,10 @@ object ScalametaParser {
   private val bigIntMaxLong = BigInt(Long.MaxValue) + 1
   private val bigIntMaxULong = bigIntMaxLong << 1
 
-  private def getTokenName[T <: Token: ClassTag]: String =
-    classTag[T].runtimeClass.getSimpleName match {
+  private def getTokenName[T <: Token: ClassTag]: String = {
+    val name = classTag[T].runtimeClass.getName
+    val simplerName = name.substring(name.lastIndexOf('.') + 1)
+    simplerName.stripPrefix("Token$").replace('$', '.') match {
       case "Semicolon" => ";"
       case "Hash" => "#"
       case "Colon" => ":"
@@ -4318,8 +4323,11 @@ object ScalametaParser {
       case "Ident" => "identifier"
       case "EOF" => "end of file"
       case "BOF" => "beginning of file"
-      case other => other.toLowerCase().stripPrefix("kw")
+      case other =>
+        val kw = other.stripPrefix("Kw").stripPrefix("Indentation.")
+        if (kw eq other) kw else kw.toLowerCase
     }
+  }
   private def syntaxExpectedMessage[T <: Token: ClassTag](tok: Token): String =
     s"`${getTokenName[T]}` expected but `${tok.name}` found"
   private def syntaxNotExpectedMessage[T <: Token: ClassTag]: String =


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala
index 24063c47b..9cca3f879 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala
@@ -120,7 +120,7 @@ class PatSuite extends ParseSuite {
 
   test("a :: ()") {
     assertPat("a :: ()")(ExtractInfix(Var(tname("a")), tname("::"), Nil))
-    val error = """|<input>:1: error: infix patterns cannot have type arguments
+    val error = """|<input>:1: error: infix patterns cannot have type arguments: not expected `[`
                    |a ::[T] ()
                    |    ^""".stripMargin
     runTestError[Pat]("a ::[T] ()", error)
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/TermSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/TermSuite.scala
index 64cd4cdea..f69627abb 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/TermSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/TermSuite.scala
@@ -301,7 +301,7 @@ class TermSuite extends ParseSuite {
 
   test("1 match { case case 1 if true => }") {
     val intercepted = intercept[ParseException](term("1 match { case case 1 if true => }"))
-    assertNoDiff(intercepted.shortMessage, "Unexpected `case`")
+    assertNoDiff(intercepted.shortMessage, "not expected `case`")
   }
 
   test("try 1")(assertTerm("try 1")(Try(int(1), Nil, None)))
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 beaf29899..253f9754a 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
@@ -268,7 +268,7 @@ class EndMarkerSuite extends BaseDottySuite {
          |  end Bar
          |end Foo
          |""".stripMargin,
-      """|<input>:2: error: expected template body
+      """|<input>:2: error: template body: `indent` expected but `\n` found
          |  trait Baz:
          |            ^""".stripMargin
     )
@@ -280,7 +280,7 @@ class EndMarkerSuite extends BaseDottySuite {
          |  trait Bar:
          |end Foo
          |""".stripMargin,
-      """|<input>:2: error: expected template body
+      """|<input>:2: error: template body: `indent` expected but `outdent` found
          |  trait Bar:
          |            ^""".stripMargin
     )
@@ -290,7 +290,7 @@ class EndMarkerSuite extends BaseDottySuite {
     runTestError[Source](
       """|trait Foo:
          |""".stripMargin,
-      """|<input>:1: error: expected template body
+      """|<input>:1: error: template body: `indent` expected but `\n` found
          |trait Foo:
          |          ^""".stripMargin
     )
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala
index a0ffb8089..5c336d7eb 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala
@@ -134,7 +134,7 @@ class PatSuite extends ParseSuite {
 
   test("a :: ()") {
     assertPat("a :: ()")(ExtractInfix(Var(tname("a")), tname("::"), Nil))
-    val error = """|<input>:1: error: infix patterns cannot have type arguments
+    val error = """|<input>:1: error: infix patterns cannot have type arguments: not expected `[`
                    |a ::[T] ()
                    |    ^""".stripMargin
     runTestError[Pat]("a ::[T] ()", error)