Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ final class OutlineParser[TypeDecl <: IMutableTypeDeclaration, Ctx](
charOffset + 1 < length && contents(charOffset + 1) == peek
}

private def peekMatch(offset: Int, peek: Char): Boolean = {
charOffset + offset < length && contents(charOffset + offset) == peek
}

private val byteBuffer = new mutable.StringBuilder(4)
private def consumeCharacter(capture: Boolean = true): Unit = {

Expand Down Expand Up @@ -647,20 +651,20 @@ final class OutlineParser[TypeDecl <: IMutableTypeDeclaration, Ctx](
}
}

private def consumeNewline(): Boolean = {
private def consumeNewline(capture: Boolean = false): Boolean = {
if (currentChar == Tokens.CarriageReturn && peekMatch(Tokens.Newline)) {
consumeCharacter(false)
consumeCharacter(false)
consumeCharacter(capture)
consumeCharacter(capture)
line += 1
lineOffset = 0
true
} else if (currentChar == Tokens.Newline) {
consumeCharacter(false)
consumeCharacter(capture)
line += 1
lineOffset = 0
true
} else {
consumeCharacter(false)
consumeCharacter(capture)
false
}
}
Expand Down Expand Up @@ -709,6 +713,12 @@ final class OutlineParser[TypeDecl <: IMutableTypeDeclaration, Ctx](

private def consumeStringLiteral(capture: Boolean = false): Unit = {
buffer.clear()

if (isAtMultilineStringLiteral) {
consumeMultilineStringLiteral(capture)
return
}

consumeCharacter(capture)

var continue = true
Expand Down Expand Up @@ -737,6 +747,48 @@ final class OutlineParser[TypeDecl <: IMutableTypeDeclaration, Ctx](
}
}

private def isAtMultilineStringLiteral: Boolean = {
peekMatch(1, Tokens.SingleQuote) &&
peekMatch(2, Tokens.SingleQuote) &&
(peekMatch(3, Tokens.Newline) || peekMatch(3, Tokens.CarriageReturn))
}

private def consumeMultilineStringLiteral(capture: Boolean): Unit = {
consumeCharacter(capture)
consumeCharacter(capture)
consumeCharacter(capture)

var continue = true
while (continue) {
val currentOffset = charOffset

currentChar match {
case Tokens.SingleQuote
if peekMatch(1, Tokens.SingleQuote) && peekMatch(2, Tokens.SingleQuote) =>
consumeCharacter(capture)
consumeCharacter(capture)
consumeCharacter(capture)
continue = false
case Tokens.BackSlash =>
consumeCharacter(capture)
if (currentChar == Tokens.SingleQuote || currentChar == Tokens.BackSlash) {
consumeCharacter(capture)
}
case Tokens.Newline | Tokens.CarriageReturn =>
consumeNewline(capture)
case _ =>
consumeCharacter(capture)
}

if (continue) {
if (atEnd()) {
throw new Exception("End of file")
} else if (currentOffset == charOffset)
throw new Exception("No Progress")
}
}
}

/** Read a token and return it
*
* @return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,92 @@ class SmokeTest extends AnyFunSpec {
assert(method.modifiers sameElements Array(Modifier("public")))
}

it("parses multi-line string literals") {
val content =
"""public class Dummy {
| public void func() {
| String a = '''
|{
| "name": "John"
|}''';
| }
|}
|""".stripMargin
val td = parse(content)

assert(td.methods.length == 1)
td.methods.head.bodyLocation.foreach(bl =>
assert(
extractLocation(content, bl) ==
"""public void func() {
| String a = '''
|{
| "name": "John"
|}''';
| }
|""".stripMargin
)
)
}

it("parses multi-line string literals with odd single quotes in the body") {
val content =
"""public class Dummy {
| public void func() {
| String a = '''
|can't
|''';
| }
|}
|""".stripMargin
val td = parse(content)

assert(td.methods.length == 1)
}

it("parses multi-line string literals with doubled single quotes in the body") {
val content =
"""public class Dummy {
| public void func() {
| String a = '''
|''two''
|''';
| }
|}
|""".stripMargin
val td = parse(content)

assert(td.methods.length == 1)
}

it("parses multi-line string literals with unbalanced braces in the body") {
val content =
"""public class Dummy {
| public void func() {
| String a = '''
|{
|''';
| }
|}
|""".stripMargin
val td = parse(content)

assert(td.methods.length == 1)
}

it("falls back to legacy string literal parsing for malformed multi-line string literals") {
val content =
"""public class Dummy {
| public void func() {
| String a = '''abc''';
| }
|}
|""".stripMargin
val td = parse(content)

assert(td.methods.length == 1)
}

it("errors on constructor without body terminated with semi-colon") {
val content =
"""public class Dummy {
Expand Down
Loading