diff --git a/.gitignore b/.gitignore index ba0b2b4c..49659ef3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ bin/ .project .cache* *~ +.bloop +.metals +metals.sbt diff --git a/scalariform/src/main/scala/scalariform/lexer/ScalaLexerException.scala b/scalariform/src/main/scala/scalariform/lexer/ScalaLexerException.scala index a452c2ae..f2277a53 100644 --- a/scalariform/src/main/scala/scalariform/lexer/ScalaLexerException.scala +++ b/scalariform/src/main/scala/scalariform/lexer/ScalaLexerException.scala @@ -1,5 +1,3 @@ package scalariform.lexer -import scalariform.parser.ScalaParserException - -class ScalaLexerException(message: String) extends ScalaParserException(message) +class ScalaLexerException(message: String) extends RuntimeException(message) diff --git a/scalariform/src/main/scala/scalariform/parser/InferredSemicolonScalaParser.scala b/scalariform/src/main/scala/scalariform/parser/InferredSemicolonScalaParser.scala index f85f394e..63568919 100644 --- a/scalariform/src/main/scala/scalariform/parser/InferredSemicolonScalaParser.scala +++ b/scalariform/src/main/scala/scalariform/parser/InferredSemicolonScalaParser.scala @@ -78,7 +78,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { if (currentTokenType == tokenType) nextToken() else - throw new ScalaParserException("Expected token " + tokenType + " but got " + currentToken) + throw new ScalaParserException("Expected token " + tokenType + " but got " + currentToken, line) var inferredSemicolons: Set[Token] = Set() @@ -305,7 +305,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { if (isIdent) nextToken() else - throw new ScalaParserException("Expected identifier, but got " + currentToken) + throw new ScalaParserException("Expected identifier, but got " + currentToken, line) private def selector() = ident() @@ -389,7 +389,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { else if (CHARACTER_LITERAL || INTEGER_LITERAL || FLOATING_POINT_LITERAL || STRING_LITERAL || SYMBOL_LITERAL || TRUE || FALSE || NULL) nextToken() else - throw new ScalaParserException("illegal literal: " + currentToken) + throw new ScalaParserException("illegal literal: " + currentToken, line) private def interpolatedString(inPattern: Boolean): Unit = { nextToken() @@ -407,7 +407,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { expr() } if (!STRING_LITERAL) // TODO: Can it be absent, as allowed by Scalac? - throw new ScalaParserException("Unexpected conclusion to string interpolation: " + currentToken) + throw new ScalaParserException("Unexpected conclusion to string interpolation: " + currentToken, line) nextToken() } @@ -453,7 +453,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { accept(RPAREN) } else { accept(LPAREN) - throw new ScalaParserException("Straggling lparen thing") + throw new ScalaParserException("Straggling lparen thing", line) } } @@ -631,7 +631,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { nextToken() template() case _ ⇒ - throw new ScalaParserException("illegal start of simple expression: " + currentToken) + throw new ScalaParserException("illegal start of simple expression: " + currentToken, line) } simpleExprRest(canApply) } @@ -826,7 +826,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case XML_START_OPEN | XML_COMMENT | XML_CDATA | XML_UNPARSED | XML_PROCESSING_INSTRUCTION ⇒ xmlLiteralPattern() case _ ⇒ - throw new ScalaParserException("illegal start of simple pattern: " + currentToken) + throw new ScalaParserException("illegal start of simple pattern: " + currentToken, line) } } @@ -1192,7 +1192,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF /* <-- for Scalariform tests */ ⇒ typeBounds() case _ ⇒ - throw new ScalaParserException("`=', `>:', or `<:' expected, but got " + currentToken) + throw new ScalaParserException("`=', `>:', or `<:' expected, but got " + currentToken, line) } } @@ -1209,7 +1209,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case CASE if lookahead(1) == CLASS ⇒ classDef() case OBJECT ⇒ objectDef() case CASE if lookahead(1) == OBJECT ⇒ objectDef() - case _ ⇒ throw new ScalaParserException("expected start of definition, but was " + currentToken) + case _ ⇒ throw new ScalaParserException("expected start of definition, but was " + currentToken, line) } } @@ -1285,7 +1285,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { if (LBRACE) templateBody() else if (LPAREN) - throw new ScalaParserException("traits or objects may not have parameters") + throw new ScalaParserException("traits or objects may not have parameters", line) else None } @@ -1314,7 +1314,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { topLevelTmplDef() case _ ⇒ if (!isStatSep) - throw new ScalaParserException("expected class or object definition") + throw new ScalaParserException("expected class or object definition", line) else None } @@ -1343,7 +1343,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { else if (isDefIntro || isModifier || AT) Some(nonLocalDefOrDcl()) else if (!isStatSep) - throw new ScalaParserException("illegal start of definition: " + currentToken) + throw new ScalaParserException("illegal start of definition: " + currentToken, line) else None acceptStatSepOpt() @@ -1355,7 +1355,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { if (isDclIntro) defOrDcl() else if (!isStatSep) - throw new ScalaParserException("illegal start of definition: " + currentToken) + throw new ScalaParserException("illegal start of definition: " + currentToken, line) if (!RBRACE) acceptStatSep() @@ -1392,7 +1392,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { } else if (isStatSep) { acceptStatSep() // <-- for inferred semi // nextToken() } else - throw new ScalaParserException("illegal start of statement: " + currentToken) + throw new ScalaParserException("illegal start of statement: " + currentToken, line) } } @@ -1441,7 +1441,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case XML_TAG_CLOSE ⇒ // End loop case _ ⇒ - throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken) + throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken, line) } } accept(XML_TAG_CLOSE) @@ -1458,7 +1458,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case LBRACE ⇒ xmlEmbeddedScala(isPattern) case _ ⇒ - throw new ScalaParserException("Expected XML attribute name or left brace: " + currentToken) + throw new ScalaParserException("Expected XML attribute name or left brace: " + currentToken, line) } } @@ -1473,7 +1473,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case XML_EMPTY_CLOSE ⇒ // End loop case _ ⇒ - throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken) + throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken, line) } } accept(XML_EMPTY_CLOSE) @@ -1506,7 +1506,7 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { case XML_UNPARSED ⇒ XmlUnparsed(nextToken()) case XML_PROCESSING_INSTRUCTION ⇒ XmlProcessingInstruction(nextToken()) case LBRACE ⇒ xmlEmbeddedScala(isPattern) - case _ ⇒ throw new ScalaParserException("Unexpected token in XML: " + currentToken) + case _ ⇒ throw new ScalaParserException("Unexpected token in XML: " + currentToken, line) } } xmlEndTag() @@ -1531,6 +1531,8 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { private var pos = 0 + private var line = 1 + private def currentToken: Token = this(pos) private def apply(pos: Int): Token = @@ -1545,6 +1547,9 @@ class InferredSemicolonScalaParser(tokens: Array[Token]) { private def nextToken(): Token = { val token = currentToken pos += 1 + val newLines = token.associatedWhitespaceAndComments.tokens + .foldLeft(0){(acc, t) => t.text.count(_ == '\n') + acc} + line += newLines if (logging) println("nextToken(): " + token + " --> " + currentToken) token diff --git a/scalariform/src/main/scala/scalariform/parser/ScalaParser.scala b/scalariform/src/main/scala/scalariform/parser/ScalaParser.scala index c6fc63a4..60c71247 100644 --- a/scalariform/src/main/scala/scalariform/parser/ScalaParser.scala +++ b/scalariform/src/main/scala/scalariform/parser/ScalaParser.scala @@ -77,7 +77,7 @@ class ScalaParser(tokens: Array[Token]) { if (currentTokenType == tokenType) nextToken() else - throw new ScalaParserException("Expected token " + tokenType + " but got " + currentToken) + throw new ScalaParserException("Expected token " + tokenType + " but got " + currentToken, line) private def acceptStatSep(): Token = currentTokenType match { case NEWLINE | NEWLINES ⇒ nextToken() @@ -339,7 +339,7 @@ class ScalaParser(tokens: Array[Token]) { if (isIdent) nextToken() else - throw new ScalaParserException("Expected identifier, but got " + currentToken) + throw new ScalaParserException("Expected identifier, but got " + currentToken, line) private def selector(): Token = ident() @@ -439,7 +439,7 @@ class ScalaParser(tokens: Array[Token]) { else if (CHARACTER_LITERAL || INTEGER_LITERAL || FLOATING_POINT_LITERAL || STRING_LITERAL || SYMBOL_LITERAL || TRUE || FALSE || NULL) exprElementFlatten2(nextToken()) else - throw new ScalaParserException("illegal literal: " + currentToken) + throw new ScalaParserException("illegal literal: " + currentToken, line) private def interpolatedString(inPattern: Boolean): StringInterpolation = { val interpolationId = nextToken() @@ -456,11 +456,11 @@ class ScalaParser(tokens: Array[Token]) { else if (THIS) makeExpr(nextToken()) else - throw new ScalaParserException("Error in string interpolation: expected block, identifier or `this'") + throw new ScalaParserException("Error in string interpolation: expected block, identifier or `this'", line) stringPartsAndScala += ((stringPart, scalaSegment)) } if (!STRING_LITERAL) // TODO: Can it be absent, as allowed by Scalac? - throw new ScalaParserException("Unexpected conclusion to string interpolation: " + currentToken) + throw new ScalaParserException("Unexpected conclusion to string interpolation: " + currentToken, line) val terminalString = nextToken() StringInterpolation(interpolationId, stringPartsAndScala.toList, terminalString) } @@ -511,7 +511,7 @@ class ScalaParser(tokens: Array[Token]) { } else { accept(LPAREN) // Seriously, WTF? - throw new ScalaParserException("Straggling lparen thing") + throw new ScalaParserException("Straggling lparen thing", line) } } @@ -785,7 +785,7 @@ class ScalaParser(tokens: Array[Token]) { val template_ = template() List(New(newToken, template_)) case _ ⇒ - throw new ScalaParserException("illegal start of simple expression: " + currentToken) + throw new ScalaParserException("illegal start of simple expression: " + currentToken, line) } simpleExprRest(firstPart, canApply) } @@ -1035,7 +1035,7 @@ class ScalaParser(tokens: Array[Token]) { case XML_START_OPEN | XML_COMMENT | XML_CDATA | XML_UNPARSED | XML_PROCESSING_INSTRUCTION ⇒ exprElementFlatten2(xmlLiteralPattern()) case _ ⇒ - throw new ScalaParserException("illegal start of simple pattern: " + currentToken) + throw new ScalaParserException("illegal start of simple pattern: " + currentToken, line) } } @@ -1460,7 +1460,7 @@ class ScalaParser(tokens: Array[Token]) { val typeBounds_ = typeBounds() Right(typeBounds_) case _ ⇒ - throw new ScalaParserException("`=', `>:', or `<:' expected, but got " + currentToken) + throw new ScalaParserException("`=', `>:', or `<:' expected, but got " + currentToken, line) } TypeDefOrDcl(typeElementFlatten3(typeToken, newLinesOpt_, name, typeParamClauseOpt_, extraTypeDclStuff)) } @@ -1479,7 +1479,7 @@ class ScalaParser(tokens: Array[Token]) { case CASE if lookahead(1) == CLASS ⇒ classDef() case OBJECT ⇒ objectDef() case CASE if lookahead(1) == OBJECT ⇒ objectDef() - case _ ⇒ throw new ScalaParserException("expected start of definition, but was " + currentToken) + case _ ⇒ throw new ScalaParserException("expected start of definition, but was " + currentToken, line) } } @@ -1585,7 +1585,7 @@ class ScalaParser(tokens: Array[Token]) { if (LBRACE) Some(templateBody().copy(newlineOpt = newLineOpt)) else if (LPAREN) - throw new ScalaParserException("traits or objects may not have parameters") + throw new ScalaParserException("traits or objects may not have parameters", line) else None } @@ -1618,7 +1618,7 @@ class ScalaParser(tokens: Array[Token]) { Some(topLevelTmplDef()) case _ ⇒ if (!isStatSep) - throw new ScalaParserException("expected class or object definition") + throw new ScalaParserException("expected class or object definition", line) else None } @@ -1652,7 +1652,7 @@ class ScalaParser(tokens: Array[Token]) { else if (isDefIntro || isModifier || AT) Some(nonLocalDefOrDcl()) else if (!isStatSep) - throw new ScalaParserException("illegal start of definition: " + currentToken) + throw new ScalaParserException("illegal start of definition: " + currentToken, line) else None val statSepOpt = acceptStatSepOpt() @@ -1670,7 +1670,7 @@ class ScalaParser(tokens: Array[Token]) { val defOrDcl_ = defOrDcl() Some(FullDefOrDcl(annotations = Nil, modifiers = Nil, defOrDcl = defOrDcl_)) } else if (!isStatSep) - throw new ScalaParserException("illegal start of definition: " + currentToken) + throw new ScalaParserException("illegal start of definition: " + currentToken, line) else None val statSepOpt = if (!RBRACE) Some(acceptStatSep()) else None @@ -1716,7 +1716,7 @@ class ScalaParser(tokens: Array[Token]) { val statSep = nextToken() statAndStatSeps += ((None, Some(statSep))) } else - throw new ScalaParserException("illegal start of statement: " + currentToken) + throw new ScalaParserException("illegal start of statement: " + currentToken, line) } rearrangeStatsAndSeps(statAndStatSeps) } @@ -1772,7 +1772,7 @@ class ScalaParser(tokens: Array[Token]) { val otherStatSeq = topStatSeq() val packageBlock = PackageBlock(packageToken, packageName, newLineOpt_, lbrace, packageBlockStats, rbrace) if (otherStatSeq.selfReferenceOpt.isDefined || otherStatSeq.firstStatOpt.isDefined) - throw new ScalaParserException("Illegal package blocks") // To avoid blowing up on -ve cases + throw new ScalaParserException("Illegal package blocks", line) // To avoid blowing up on -ve cases StatSeq(None, Some(packageBlock), otherStatSeq.otherStats) } } @@ -1806,7 +1806,7 @@ class ScalaParser(tokens: Array[Token]) { case XML_TAG_CLOSE ⇒ // End loop case _ ⇒ - throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken) + throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken, line) } } val tagClose = accept(XML_TAG_CLOSE) @@ -1824,7 +1824,7 @@ class ScalaParser(tokens: Array[Token]) { case LBRACE ⇒ Right(xmlEmbeddedScala(isPattern)) case _ ⇒ - throw new ScalaParserException("Expected XML attribute name or left brace: " + currentToken) + throw new ScalaParserException("Expected XML attribute name or left brace: " + currentToken, line) } XmlAttribute(name, whitespaceOption, equals, whitespaceOption2, valueOrEmbeddedScala) } @@ -1844,7 +1844,7 @@ class ScalaParser(tokens: Array[Token]) { case XML_EMPTY_CLOSE ⇒ // End loop case _ ⇒ - throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken) + throw new ScalaParserException("Expected XML attribute or end of tag: " + currentToken, line) } } val emptyClose = accept(XML_EMPTY_CLOSE) @@ -1881,7 +1881,7 @@ class ScalaParser(tokens: Array[Token]) { case XML_UNPARSED ⇒ XmlUnparsed(nextToken()) case XML_PROCESSING_INSTRUCTION ⇒ XmlProcessingInstruction(nextToken()) case LBRACE ⇒ xmlEmbeddedScala(isPattern) - case _ ⇒ throw new ScalaParserException("Unexpected token in XML: " + currentToken) + case _ ⇒ throw new ScalaParserException("Unexpected token in XML: " + currentToken, line) } contents += content } @@ -1902,7 +1902,7 @@ class ScalaParser(tokens: Array[Token]) { case XML_CDATA ⇒ XmlCDATA(nextToken()) case XML_UNPARSED ⇒ XmlUnparsed(nextToken()) case XML_PROCESSING_INSTRUCTION ⇒ XmlProcessingInstruction(nextToken()) - case _ ⇒ throw new ScalaParserException("Expected XML: " + currentToken) + case _ ⇒ throw new ScalaParserException("Expected XML: " + currentToken, line) } val first = xmlContent() val otherContents = ListBuffer[XmlContents]() @@ -1921,6 +1921,8 @@ class ScalaParser(tokens: Array[Token]) { private def xmlLiteralPattern() = xml(isPattern = true) private var pos = 0 + + private var line = 1 private def currentToken: Token = this(pos) @@ -1936,11 +1938,13 @@ class ScalaParser(tokens: Array[Token]) { private def nextToken(): Token = { val token = currentToken pos += 1 + val newLines = token.associatedWhitespaceAndComments.tokens + .foldLeft(0){(acc, t) => t.text.count(_ == '\n') + acc} + line += newLines if (logging) println("nextToken(): " + token + " --> " + currentToken) token } - private def lookahead(n: Int): TokenType = this(pos + n).tokenType private implicit def tokenType2Boolean(tokenType: TokenType): Boolean = currentTokenType == tokenType diff --git a/scalariform/src/main/scala/scalariform/parser/ScalaParserException.scala b/scalariform/src/main/scala/scalariform/parser/ScalaParserException.scala index 946142d5..102f6eea 100644 --- a/scalariform/src/main/scala/scalariform/parser/ScalaParserException.scala +++ b/scalariform/src/main/scala/scalariform/parser/ScalaParserException.scala @@ -1,3 +1,3 @@ package scalariform.parser -class ScalaParserException(message: String) extends RuntimeException(message) +class ScalaParserException(val message: String, val line: Int) extends RuntimeException(message) diff --git a/scalariform/src/test/scala/scalariform/parser/ParserTest.scala b/scalariform/src/test/scala/scalariform/parser/ParserTest.scala index 96b3dda8..4a6c3aaa 100644 --- a/scalariform/src/test/scala/scalariform/parser/ParserTest.scala +++ b/scalariform/src/test/scala/scalariform/parser/ParserTest.scala @@ -35,7 +35,24 @@ class ParserTest extends FlatSpec with Matchers { } "Parser" should "throw a parse exception in bad package blocks" in { - an [ScalaParserException] should be thrownBy parseCompilationUnit("package a {} package b {}") + (the [ScalaParserException] thrownBy parseCompilationUnit("package a {} package b {}")).line shouldBe 1 + } + + "Parser" should "indicate the line number" in { + (the [ScalaParserException] thrownBy parseCompilationUnit( + """class Trivial { + def method(): Unit = {) + }""" + )).line shouldBe 2 + + (the [ScalaParserException] thrownBy parseExpression( + """args(0) match { + case "blah" => + val x = args(0) + case _ } // syntax error here + println("not blah") + } + """)).line shouldBe 4 } // issue #44