diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala index 5d54245d93..7bad1a8e14 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala @@ -118,35 +118,27 @@ class FormatOps( classifier: Classifier[T, A], ): Option[FormatToken] = findFirst(start, end.start)(x => classifier(x.right)) - final def rhsOptimalToken(start: FormatToken, end: Int = Int.MaxValue)( - implicit style: ScalafmtConfig, - ): T = findTokenWith(start, next) { start => - start.right match { - case t if t.end >= end => Some(start.left) - case _ if start.hasBlankLine => Some(start.left) - case _: T.RightParen - if start.left.is[T.RightParen] || start.left.is[T.LeftParen] => None - case _: T.RightBracket if start.left.is[T.RightBracket] => None - case _: T.LeftParen if !leftParenStartsNewBlockOnRight(start) => None - case _: T.Comma | _: T.Semicolon | _: T.RightArrow | _: T.Equals => None - case c: T.Comment - if start.noBreak && - (!start.left.is[T.LeftParen] || - hasBreakAfterRightBeforeNonComment(start)) => Some(c) - case _ => Some(start.left) - } - }.fold(_.right, identity) - @tailrec - final def endOfSingleLineBlockOnLeft( - start: FormatToken, - )(implicit style: ScalafmtConfig): FormatToken = { + final def getSlbEndOnLeft(start: FormatToken, end: Int = Int.MaxValue)( + implicit style: ScalafmtConfig, + ): FormatToken = { val endFound = start.right match { + case t if t.end >= end => true case _: T.EOF => true case _: T.Comma | _: T.Semicolon | _: T.RightArrow | _: T.Equals => false case _ if start.hasBlankLine => true - case _: T.LeftParen => leftParenStartsNewBlockOnRight(start) - case _: T.RightParen => !start.left.is[T.LeftParen] + case _ + if !style.newlines.formatInfix && + (isInfixOp(start.rightOwner) || isInfixOpOnLeft(start)) => + start.hasBreak + case _: T.LeftParen if !leftParenStartsNewBlockOnRight(start) => false + case _: T.RightBracket if start.left.is[T.RightBracket] => false + case _: T.RightParen => start.left match { + case _: T.LeftParen => false + case _: T.RightParen => !style.newlines.fold && + style.danglingParentheses.atSite(start.rightOwner, orElse = true) + case _ => true + } case _: T.Comment => if (start.noBreak) { val nft = next(start) @@ -154,16 +146,15 @@ class FormatOps( return nft // RETURN!!! } true - case _ => style.newlines.formatInfix || start.hasBreak || - !(isInfixOp(start.rightOwner) || isInfixOpOnLeft(start)) + case _ => true } - if (endFound) start else endOfSingleLineBlockOnLeft(next(start)) + if (endFound) start else getSlbEndOnLeft(next(start)) } final def endOfSingleLineBlock(start: FormatToken)(implicit style: ScalafmtConfig, - ): T = endOfSingleLineBlockOnLeft(start).left + ): T = getSlbEndOnLeft(start).left final def isInfixOpOnLeft(ft: FormatToken): Boolean = isInfixOp(prevNonComment(ft).leftOwner) @@ -1535,7 +1526,7 @@ class FormatOps( fileLine: FileLine, ) = { val spacePolicy = policy | penalize(penalty) - val miniSlbEnd = rhsOptimalToken(next(ft), blastFT.right.end) + val miniSlbEnd = getSlbEndOnLeft(next(ft), blastFT.right.end).left val slbLite = style.newlines.keep val opt = if (slbLite) miniSlbEnd else blast Split(Space, 0).withSingleLineNoOptimal(miniSlbEnd, noSyntaxNL = true) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala index 590c2a94f9..d80ad6e4e8 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala @@ -397,7 +397,8 @@ class Router(formatOps: FormatOps) { // copy logic from `( ...`, binpack=never, defining `slbSplit` val isBeforeOpenParen = style.newlines.isBeforeOpenParenCallSite Split(NoSplit, 0).withSingleLine( - if (isBeforeOpenParen) closeFT.left else rhsOptimalToken(closeFT), + if (isBeforeOpenParen) closeFT.left + else endOfSingleLineBlock(closeFT), noSyntaxNL = true, killOnFail = true, noOptimal = style.newlines.keep, @@ -914,7 +915,7 @@ class Router(formatOps: FormatOps) { else style.newlines.isBeforeOpenParenCallSite val optimal: T = if (isBeforeOpenParen) close - else if (!defnSite || isBracket) rhsOptimalToken(afterClose) + else if (!defnSite || isBracket) endOfSingleLineBlock(afterClose) else defnSiteLastToken(afterClose, leftOwner) val wouldDangle = onlyConfigStyle || mustDangleForTrailingCommas || @@ -1409,7 +1410,8 @@ class Router(formatOps: FormatOps) { .Block(List(_: Term.FunctionTerm | _: Term.PartialFunction)) => Seq(Split(Newline, 0)) case _ => - val breakAfter = rhsOptimalToken(next(nextNonCommentSameLine(ft))) + val breakAfter = + endOfSingleLineBlock(next(nextNonCommentSameLine(ft))) val multiLine = decideNewlinesOnlyAfterToken(breakAfter) ==> decideNewlinesOnlyBeforeClose(close) Seq( @@ -1455,7 +1457,7 @@ class Router(formatOps: FormatOps) { // copy logic from `( ...`, binpack=never, defining `slbSplit` val isBeforeOpenParen = style.newlines.isBeforeOpenParenCallSite val optimal: T = - if (isBeforeOpenParen) rbft.left else rhsOptimalToken(rbft) + if (isBeforeOpenParen) rbft.left else endOfSingleLineBlock(rbft) val noOptimal = style.newlines.keep Split(NoSplit, 0).withSingleLine(optimal, noOptimal = noOptimal) } @@ -1681,7 +1683,7 @@ class Router(formatOps: FormatOps) { def getSlbEnd() = { val nft = nextNonCommentSameLineAfter(ft) val eft = if (nft.noBreak) nextNonCommentSameLineAfter(nft) else nft - rhsOptimalToken(eft) + endOfSingleLineBlock(eft) } def shouldKillOnFail() = (style.binPack.callSite ne BinPack.Site.Never) && diff --git a/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat index 5d06efd527..91aee6e2f7 100644 --- a/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat +++ b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat @@ -269,8 +269,9 @@ object a { >>> object a { lowClass.isJavaDefined && highClass.isJavaDefined && { // skip if both are java-defined, and - lowClass - .isNonBottomSubClass(highClass) || { // - low <:< high, which means they are overrides in Java and javac is doing the check; or + lowClass.isNonBottomSubClass( + highClass + ) || { // - low <:< high, which means they are overrides in Java and javac is doing the check; or base.info.parents.tail.forall { p => // - every mixin parent is unrelated to (not a subclass of) low and high, i.e., val psym =