Skip to content
Closed
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 @@ -204,7 +204,8 @@ public J visitTry(J.Try tryable, PrintOutputCapture<P> p) {
p.append("catch {");
for (J.Try.Catch aCatch : tryable.getCatches()) {
J.VariableDeclarations varDecl = aCatch.getParameter().getTree();
p.append("\n case");
visitSpace(aCatch.getParameter().getPrefix(), Space.Location.CONTROL_PARENTHESES_PREFIX, p);
p.append("case");
if (!varDecl.getVariables().isEmpty()) {
visit(varDecl.getVariables().get(0).getName(), p);
}
Expand All @@ -217,7 +218,8 @@ public J visitTry(J.Try tryable, PrintOutputCapture<P> p) {
visit(stmt, p);
}
}
p.append("\n}");
visitSpace(firstCatch.getBody().getEnd(), Space.Location.BLOCK_END, p);
p.append("}");
}
if (tryable.getPadding().getFinally() != null) {
visitSpace(tryable.getPadding().getFinally().getBefore(), Space.Location.TRY_FINALLY, p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class ScalaTreeVisitor(
case func: untpd.Function => visitFunction(func)
case typed: Trees.Typed[?] => visitTyped(typed)
case tuple: untpd.Tuple => visitTuple(tuple)
case parsedTry: untpd.ParsedTry => visitParsedTry(parsedTry)
case tryTree: Trees.Try[?] => visitTryTree(tryTree)
case matchTree: Trees.Match[?] => visitMatchTree(matchTree)
case thisTree: Trees.This[?] => visitThis(thisTree)
Expand Down Expand Up @@ -5257,6 +5258,126 @@ class ScalaTreeVisitor(
new J.Try(Tree.randomId(), prefix, Markers.EMPTY, null, body, catches, finallyBlock)
}


private def visitParsedTry(parsedTry: untpd.ParsedTry): J = {
val savedCursor = cursor
try { visitParsedTryImpl(parsedTry) } catch { case _: Exception => cursor = savedCursor; visitUnknown(parsedTry) }
}

private def visitParsedTryImpl(parsedTry: untpd.ParsedTry): J.Try = {
val prefix = extractPrefix(parsedTry.span)
val tryStart = Math.max(0, parsedTry.span.start - offsetAdjustment)
if (tryStart >= cursor && tryStart + 3 <= source.length) cursor = tryStart + 3

val body = visitTree(parsedTry.expr) match {
case block: J.Block => block
case expr: Expression =>
val stmts = new util.ArrayList[JRightPadded[Statement]]()
stmts.add(JRightPadded.build(expr.asInstanceOf[Statement]))
new J.Block(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(false), stmts, Space.EMPTY)
case _ => return visitUnknown(parsedTry).asInstanceOf[J.Try]
}

val cases: List[Trees.CaseDef[?]] = parsedTry.handler match {
case m: Trees.Match[?] => m.cases.asInstanceOf[List[Trees.CaseDef[?]]]
case _ => scala.collection.immutable.Nil
}

val catches = new util.ArrayList[J.Try.Catch]()
if (!parsedTry.handler.isEmpty && parsedTry.handler.span.exists) {
val catchSearch = if (cursor < source.length) source.substring(cursor, Math.min(cursor + 50, source.length)) else ""
val catchIdx = catchSearch.indexOf("catch")
val catchPrefix = if (catchIdx > 0) Space.format(catchSearch.substring(0, catchIdx)) else Space.EMPTY
if (catchIdx >= 0) cursor = cursor + catchIdx + 5
val braceSearch = if (cursor < source.length) source.substring(cursor, Math.min(cursor + 20, source.length)) else ""
val braceIdx = braceSearch.indexOf('{')
if (braceIdx >= 0) cursor = cursor + braceIdx + 1

for (caseDef <- cases) {
// Extract space before "case" keyword (e.g., newline + indentation)
val casePrefixSpace = extractPrefix(caseDef.span)
val caseStart = Math.max(0, caseDef.span.start - offsetAdjustment)
if (caseStart >= cursor) cursor = caseStart
val caseSearch = if (cursor < source.length) source.substring(cursor, Math.min(cursor + 20, source.length)) else ""
val caseKwIdx = caseSearch.indexOf("case")
if (caseKwIdx >= 0) cursor = cursor + caseKwIdx + 4

val arrowSearch = if (cursor < source.length) source.substring(cursor, Math.min(cursor + 200, source.length)) else ""
val arrowIdx = arrowSearch.indexOf("=>")

val paramName = caseDef.pat match {
case bind: Trees.Bind[?] => bind.name.toString
case typed: Trees.Typed[?] => typed.expr match {
case id: Trees.Ident[?] => id.name.toString
case _ => "_"
}
case _ => extractSource(caseDef.pat.span)
}
val paramType = caseDef.pat match {
case bind: Trees.Bind[?] => bind.body match {
case typed: Trees.Typed[?] => visitTree(typed.tpt) match { case tt: TypeTree => tt; case id: J.Identifier => id; case _ => null }
case _ => null
}
case typed: Trees.Typed[?] =>
{ val colonSearch = source.indexOf(':', Math.max(0, typed.expr.span.end - offsetAdjustment)); if (colonSearch >= 0) cursor = colonSearch + 1 }
visitTree(typed.tpt) match { case tt: TypeTree => tt; case id: J.Identifier => id; case _ => null }
case _ => null
}
updateCursor(caseDef.pat.span.end)
if (arrowIdx >= 0) { val a = source.indexOf("=>", cursor); if (a >= 0) cursor = a + 2 }

val paramId = new J.Identifier(Tree.randomId(), Space.format(" "), Markers.EMPTY, Collections.emptyList(), paramName, null, null)
val namedVar = new J.VariableDeclarations.NamedVariable(Tree.randomId(), Space.EMPTY, Markers.EMPTY, paramId, Collections.emptyList(), null, null)
val varDecl = new J.VariableDeclarations(Tree.randomId(), Space.EMPTY, Markers.EMPTY,
Collections.emptyList(), Collections.emptyList(), paramType, null, Collections.emptyList(),
Collections.singletonList(JRightPadded.build(namedVar)))
val controlParens = new J.ControlParentheses[J.VariableDeclarations](Tree.randomId(), casePrefixSpace, Markers.EMPTY, JRightPadded.build(varDecl))

val caseBody = visitTree(caseDef.body) match {
case block: J.Block => block
case expr: Expression =>
val s = new util.ArrayList[JRightPadded[Statement]](); s.add(JRightPadded.build(expr.asInstanceOf[Statement]))
new J.Block(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(false), s, Space.EMPTY)
case stmt: Statement =>
val s = new util.ArrayList[JRightPadded[Statement]](); s.add(JRightPadded.build(stmt))
new J.Block(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(false), s, Space.EMPTY)
case _ => new J.Block(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(false), new util.ArrayList(), Space.EMPTY)
}
updateCursor(caseDef.span.end)
catches.add(new J.Try.Catch(Tree.randomId(), catchPrefix, Markers.EMPTY, controlParens, caseBody))
}
// Extract closing brace prefix and store it in the first catch's body end space
val closeBracePrefix = if (cursor < source.length) {
val r = source.substring(cursor, Math.min(cursor + 50, source.length))
val ci = r.indexOf('}')
if (ci > 0) { val space = Space.format(r.substring(0, ci)); cursor = cursor + ci + 1; space }
else if (ci == 0) { cursor = cursor + 1; Space.EMPTY }
else Space.EMPTY
} else Space.EMPTY
if (!catches.isEmpty) {
val firstCatch = catches.get(0)
val updatedBody = firstCatch.getBody.withEnd(closeBracePrefix)
catches.set(0, firstCatch.withBody(updatedBody))
}
}

val finallyBlock: JLeftPadded[J.Block] = if (!parsedTry.finalizer.isEmpty && parsedTry.finalizer.span.exists) {
val fs = if (cursor < source.length) source.substring(cursor, Math.min(cursor + 50, source.length)) else ""
val fi = fs.indexOf("finally"); val fSpace = if (fi > 0) Space.format(fs.substring(0, fi)) else Space.EMPTY
if (fi >= 0) cursor = cursor + fi + 7
val fb = visitTree(parsedTry.finalizer) match {
case block: J.Block => block
case expr: Expression =>
val s = new util.ArrayList[JRightPadded[Statement]](); s.add(JRightPadded.build(expr.asInstanceOf[Statement]))
new J.Block(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(false), s, Space.EMPTY)
case _ => null
}
if (fb != null) JLeftPadded.build(fb).withBefore(fSpace) else null
} else null

updateCursor(parsedTry.span.end)
new J.Try(Tree.randomId(), prefix, Markers.EMPTY, null, body, catches, finallyBlock)
}
private def visitMatchTree(matchTree: Trees.Match[?]): J = {
val savedCursor = cursor
try { visitMatchImpl(matchTree) } catch { case _: Exception => cursor = savedCursor; visitUnknown(matchTree) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.scala.tree;

import org.junit.jupiter.api.Test;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.scala.Assertions.scala;

class ParsedTryTest implements RewriteTest {

@Test
void tryFinallyWithThrow() {
rewriteRun(
scala(
"""
object Test {
try {
println("risky")
} finally {
throw new RuntimeException("fail")
}
}
"""
)
);
}

@Test
void tryCatchThrowable() {
rewriteRun(
scala(
"""
object Test {
try {
println("risky")
} catch {
case e: Throwable => println("caught")
}
}
"""
)
);
}
}