From 09fc3af2370f490f72392398a6e29970d63fa23b Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:32:31 -0800 Subject: [PATCH] Use FormatToken in policies, indents etc. This way, the checks are more precise, and we reduce the probability of comparing with a token that has been removed. --- .../scalafmt/internal/BestFirstSearch.scala | 33 +- .../org/scalafmt/internal/FormatOps.scala | 399 ++++++++-------- .../org/scalafmt/internal/FormatTokens.scala | 3 + .../scala/org/scalafmt/internal/Indent.scala | 27 +- .../scala/org/scalafmt/internal/ModExt.scala | 4 +- .../scala/org/scalafmt/internal/Policy.scala | 91 ++-- .../scala/org/scalafmt/internal/Router.scala | 450 +++++++++--------- .../scala/org/scalafmt/internal/Split.scala | 28 +- .../scala/org/scalafmt/internal/State.scala | 12 +- .../scala/org/scalafmt/util/PolicyOps.scala | 85 ++-- .../scala/org/scalafmt/util/TreeOps.scala | 4 +- 11 files changed, 557 insertions(+), 579 deletions(-) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala index 7ae2a6f89..402809fe7 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala @@ -36,20 +36,20 @@ private class BestFirstSearch private (range: Set[Range])(implicit var stats = new StateStats(tokens, initStyle.runner) - private def getBlockCloseToRecurse(ft: FT, stop: T)(implicit + private def getBlockCloseToRecurse(ft: FT, stop: Int)(implicit style: ScalafmtConfig, - ): Option[T] = getEndOfBlock(ft, parensToo = true).collect { - case close if close.left != stop && { + ): Option[Int] = getEndOfBlock(ft, parensToo = true).collect { + case close if close.idx < stop && { // Block must span at least 3 lines to be worth recursing. tokens.width(ft, close) > style.maxColumn * 3 - } => close.left + } => close.idx } private val memo = mutable.Map.empty[Long, Option[State]] def shortestPathMemo( start: State, - stop: T, + stop: Int, depth: Int, isOpt: Boolean, ): Option[Option[State]] = { @@ -69,7 +69,7 @@ private class BestFirstSearch private (range: Set[Range])(implicit */ def shortestPath( start: State, - stop: T, + stop: Int, depth: Int = 0, isOpt: Boolean = false, ): Either[State, State] = { @@ -85,8 +85,7 @@ private class BestFirstSearch private (range: Set[Range])(implicit val splitToken = tokens(idx) val leftTok = splitToken.left - if (splitToken.right.start > stop.start && leftTok.start < leftTok.end) - return Right(curr) + if (splitToken.idx >= stop && !leftTok.isEmpty) return Right(curr) implicit val style = styleMap.at(splitToken) import style.runner.optimizer @@ -187,7 +186,7 @@ private class BestFirstSearch private (range: Set[Range])(implicit ) tokens(nextNextState.depth) else { val optEnd = end - if (optEnd ne null) optEnd else tokens(opt.token) + if (optEnd ne null) optEnd else opt.token } } @@ -196,18 +195,17 @@ private class BestFirstSearch private (range: Set[Range])(implicit queue: StateQueue, style: ScalafmtConfig, ): Either[State, State] = { - val optEnd = tokens(opt.token) val nextNextState = - if (opt.token.end <= tokens(nextState.depth).left.end) nextState + if (opt.token.idx <= nextState.depth) nextState else if ( - tokens.width(tokens(nextState.depth), optEnd) > 3 * style.maxColumn - ) return Left(killOnFail(opt.killOnFail)(optEnd)) + tokens.width(tokens(nextState.depth), opt.token) > 3 * style.maxColumn + ) return Left(killOnFail(opt.killOnFail)(opt.token)) else { val res = - shortestPath(nextState, opt.token, queue.nested + 1, isOpt = true) + shortestPath(nextState, opt.token.idx, queue.nested + 1, isOpt = true) res match { case Right(x) => x - case Left(x) => return Left(killOnFail(opt, optEnd, x)) + case Left(x) => return Left(killOnFail(opt, opt.token, x)) } } def checkPenalty(state: State, orElse: => Either[State, State]) = @@ -216,7 +214,7 @@ private class BestFirstSearch private (range: Set[Range])(implicit else orElse traverseSameLine(nextNextState) match { case x @ Left(s) => - if (s eq null) Left(killOnFail(opt, optEnd, nextNextState)) + if (s eq null) Left(killOnFail(opt, opt.token, nextNextState)) else checkPenalty(s, x) case x @ Right(s) => checkPenalty(s, if (opt.recurseOnly) Left(s) else x) } @@ -270,8 +268,7 @@ private class BestFirstSearch private (range: Set[Range])(implicit def getBestPath: SearchResult = { initStyle.runner.event(FormatEvent.Routes(routes)) val state = { - val endToken = topSourceTree.tokens.last - def run = shortestPath(State.start, endToken) + def run = shortestPath(State.start, Int.MaxValue) run.getOrElse { stats.retry.flatMap { x => stats = x 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 e18a1846d..6dbf0cd11 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 @@ -52,29 +52,22 @@ class FormatOps( @inline def owners(token: T): Tree = ownersMap(hash(token)) - @inline - final def findFirst(start: FT, end: T)(f: FT => Boolean): Option[FT] = - findFirst(start, end.end)(f) - @tailrec - final def findFirst(start: FT, end: Int)(f: FT => Boolean): Option[FT] = - if (start.left.end >= end) None + final def findFirst(start: FT, end: FT)(f: FT => Boolean): Option[FT] = + if (start.idx >= end.idx) None else if (f(start)) Some(start) else { val next_ = next(start) if (next_ == start) None else findFirst(next_, end)(f) } - def findFirstOnRight[A](start: FT, end: T)(implicit + def findFirstOnRight[A](start: FT, end: FT)(implicit classifier: Classifier[T, A], - ): Option[FT] = findFirst(start, end.start)(x => classifier(x.right)) + ): Option[FT] = findFirst(start, prev(end))(x => classifier(x.right)) @tailrec - final def getSlbEndOnLeft(start: FT, end: Int = Int.MaxValue)(implicit - style: ScalafmtConfig, - ): FT = { + final def getSlbEndOnLeft(start: FT)(implicit style: ScalafmtConfig): FT = { val nft = start.right match { - case t if t.end >= end => start case _: T.EOF => start case _: T.Comma | _: T.Semicolon | _: T.RightArrow | _: T.Equals | _: T.Interpolation.Start | _: T.Interpolation.SpliceEnd | @@ -148,9 +141,6 @@ class FormatOps( else getSlbEndOnLeft(if (nft ne null) nft else next(start)) } - final def endOfSingleLineBlock(start: FT)(implicit style: ScalafmtConfig): T = - getSlbEndOnLeft(start).left - /** js.native is very special in Scala.js. * * Context: https://github.com/scalameta/scalafmt/issues/108 @@ -170,22 +160,24 @@ class FormatOps( .fold(TokenRanges.empty)(other => TokenRanges(TokenRange(ft, other))) def parensTuple(tree: Tree): TokenRanges = parensTuple(getLast(tree)) - def insideBlock[A](start: FT, end: T)(implicit + def insideBlock[A](start: FT, end: FT)(implicit classifier: Classifier[T, A], ): TokenRanges = insideBlock(start, end, x => classifier(x.left)) - def insideBlock(start: FT, end: T, matches: FT => Boolean): TokenRanges = + def insideBlock(start: FT, end: FT, matches: FT => Boolean): TokenRanges = insideBlock(x => if (matches(x)) matchingOpt(x.left) else None)(start, end) - def insideBracesBlock(start: FT, end: T, parensToo: Boolean = false)(implicit + def insideBracesBlock(start: FT, end: FT, parensToo: Boolean = false)(implicit style: ScalafmtConfig, ): TokenRanges = insideBlock(x => getEndOfBlock(x, parensToo))(start, end) - def insideBlock(matches: FT => Option[FT])(start: FT, end: T): TokenRanges = { + def insideBlock( + matches: FT => Option[FT], + )(start: FT, end: FT): TokenRanges = { var result = TokenRanges.empty @tailrec - def run(tok: FT): Unit = if (tok.left.start < end.start) { + def run(tok: FT): Unit = if (tok.idx < end.idx) { val nextTokOpt = matches(tok).flatMap { closeFt => if (tok.left.start >= closeFt.left.end) None else { @@ -217,14 +209,14 @@ class FormatOps( } @inline - def splitOneArgOneLine(close: T, owner: Tree)(implicit + def splitOneArgOneLine(close: FT, owner: Tree)(implicit fileLine: FileLine, style: ScalafmtConfig, ): Policy = if (style.poorMansTrailingCommasInConfigStyle) Policy - .before(close, prefix = "B[,]")(splitOneArgPerLineBeforeComma(owner)) + .beforeLeft(close, prefix = "B[,]")(splitOneArgPerLineBeforeComma(owner)) else Policy - .before(close, prefix = "A[,]")(splitOneArgPerLineAfterComma(owner)) + .beforeLeft(close, prefix = "A[,]")(splitOneArgPerLineAfterComma(owner)) def splitOneArgPerLineBeforeComma(owner: Tree): Policy.Pf = { // TODO(olafur) clear queue between arguments, they are independent. @@ -250,20 +242,18 @@ class FormatOps( getOneArgPerLineSplitsAfterComma(right, splits) } - def splitOneArgPerLineAfterCommaOnBreak(comma: T)(implicit + def splitOneArgPerLineAfterCommaOnBreak(comma: FT)(implicit fileLine: FileLine, ): Policy = splitOneArgPerLineAfterCommaOnBreak(TokenRanges.empty)(comma) - def splitOneArgPerLineAfterCommaOnBreak( - exclude: TokenRanges, - )(comma: T)(implicit fileLine: FileLine): Policy = { - val policy = Policy.after(comma, prefix = "NL->A[,]") { - case Decision(t @ FT(`comma`, right, _), splits) - if !right.is[T.Comment] || t.hasBreak => - getOneArgPerLineSplitsAfterComma(right, splits) + def splitOneArgPerLineAfterCommaOnBreak(exclude: TokenRanges)(comma: FT)( + implicit fileLine: FileLine, + ): Policy = Policy ? (comma.right.is[T.Comment] && comma.noBreak) || + delayedBreakPolicy(Policy.End < comma, exclude) { + Policy.onRight(comma, prefix = "NL->A[,]") { case Decision(`comma`, ss) => + getOneArgPerLineSplitsAfterComma(comma.right, ss) + } } - delayedBreakPolicy(Policy.End < comma, exclude)(policy) - } private def getOneArgPerLineSplitsAfterComma(r: T, s: Seq[Split]) = if (r.is[T.LeftBrace]) SplitTag.OneArgPerLine.activateOnly(s) @@ -306,9 +296,10 @@ class FormatOps( getTemplateGroups(template).flatMap(iter) } - def getBreakBeforeElsePolicy(els: T): Policy = Policy - .on(els, prefix = "ELSE") { case d @ Decision(FT(_, `els`, _), _) => - d.onlyNewlinesWithFallback(Split(Newline, 0)) + def getBreakBeforeElsePolicy(beforeElse: FT): Policy = Policy.End <= + beforeElse ==> Policy.onRight(beforeElse, prefix = "ELSE") { + case d @ Decision(`beforeElse`, _) => d + .onlyNewlinesWithFallback(Split(Newline, 0)) } def getBreakBeforeElsePolicy(term: Term.If): Policy = getElseToken(term) @@ -319,34 +310,32 @@ class FormatOps( getBreakBeforeElsePolicy(els) ==> res } - private final def getElseToken( - term: Term.If, - ): Option[(FT, Option[T.KwElse])] = getHeadOpt(term.elsep).map { ftElsep => - val elsOpt = prevNonCommentBefore(ftElsep) match { - case ft @ FT(els: T.KwElse, _, _) => - val ok = initStyle.newlines.alwaysBeforeElseAfterCurlyIf || ! { - val pft = prev(ft) - pft.leftOwner.is[Term.Block] && pft.left.is[T.RightBrace] && - !prevNonCommentSameLineBefore(pft).left.is[T.LeftBrace] && - matchingOpt(pft.left).exists { lb => - prev(lb).left.start < term.thenp.pos.start + private final def getElseToken(term: Term.If): Option[(FT, Option[FT])] = + tokenJustBeforeOpt(term.elsep).map { ftElsep => + val elsOpt = prevBeforeNonComment(ftElsep) match { + case ft @ FT(_, _: T.KwElse, _) => + val ok = initStyle.newlines.alwaysBeforeElseAfterCurlyIf || ! { + ft.leftOwner.is[Term.Block] && ft.left.is[T.RightBrace] && + !prevNonCommentSameLineBefore(ft).left.is[T.LeftBrace] && + matchingOpt(ft.left).exists { lb => + prev(lb).left.start < term.thenp.pos.start + } } - } - if (ok) Some(els) else None - case _ => None + if (ok) Some(ft) else None + case _ => None + } + (ftElsep, elsOpt) } - (ftElsep, elsOpt) - } @tailrec - private final def getElseChain(term: Term.If, res: List[T]): List[T] = + private final def getElseChain(term: Term.If, res: List[FT]): List[FT] = getElseToken(term) match { case Some((ftElsep, elsOpt)) => val newRes = elsOpt.fold(res)(_ :: res) term.elsep match { case t: Term.If => getElseChain(t, newRes) case b @ Term.Block((t: Term.If) :: Nil) - if !areMatching(ftElsep.left)(getLastToken(b)) => + if !areMatching(ftElsep.right)(getLast(b).left) => getElseChain(t, newRes) case _ => newRes } @@ -516,19 +505,19 @@ class FormatOps( private val isAfterOp = ft.meta.leftOwner eq app.op private val beforeLhs = !isAfterOp && ft.left.start < app.pos.start private val isFirstOp = beforeLhs || isLeftInfix - private val fullExpire = getLastEnclosedToken(fullInfix) + private val fullExpire = getLastExceptParen(fullInfix) private val assignBodyExpire = { val prevFt = tokenBefore(fullInfix) val prevOwner = prevFt.meta.leftOwner prevFt.left match { - case _: T.Equals => Some(getLastToken(prevOwner)) + case _: T.Equals => Some(getLast(prevOwner)) case _: T.LeftParen | _: T.LeftBracket if fullInfix.parent.contains(prevOwner) && !(prevOwner match { case po: Member.ArgClause => po.parent.exists(isInfixApp) case po => isInfixApp(po) }) && isSeqSingle(getArgsOrNil(prevOwner)) => - Some(getLastToken(fullInfix)) + Some(getLast(fullInfix)) case _ => None } } @@ -616,7 +605,7 @@ class FormatOps( val (nlIndent, nlPolicy) = { def policy(triggers: T*)(implicit fileLine: FileLine) = - Policy ? triggers.isEmpty || Policy.on(fullExpire, prefix = "INF") { + Policy ? triggers.isEmpty || Policy.onLeft(fullExpire, prefix = "INF") { case Decision(t: FT, s) if isInfixOp(t.meta.leftOwner) || isInfixOp(t.meta.rightOwner) && @@ -653,9 +642,12 @@ class FormatOps( )(implicit ft: FT): Seq[Split] = { val maxPrecedence = if (isAfterOp) infixSequenceMaxPrecedence(fullInfix) else 0 // 0 unused + val breakPenalty = if (isAfterOp) maxPrecedence - app.precedence else 1 + val closeOpt = matchingOpt(ft.right) - val expiresOpt = - if (closeOpt.isDefined) None + val finalExpireCost = fullExpire -> 0 + val expires = + if (closeOpt.isDefined) finalExpireCost :: Nil else { val res = mutable.Buffer.empty[Member.Infix] findNextInfixes(fullInfix, app.lhs, res)( @@ -663,22 +655,22 @@ class FormatOps( else x => !isEnclosedWithinParensOrBraces(x.lhs), ) val infixes = if (isAfterOp) res.toSeq.drop(1) else res.toSeq - if (infixes.isEmpty) None + if (infixes.isEmpty) finalExpireCost :: Nil else { - val res = infixes.foldLeft(Seq.empty[(T, Int)]) { case (out, ia) => + val out = new mutable.ListBuffer[(FT, Int)] + var minCost = Int.MaxValue + infixes.foreach { ia => val cost = maxPrecedence - ia.precedence - if (out.nonEmpty && out.head._2 <= cost) out - else (getMidInfixToken(ia) -> cost) +: out + if (cost < minCost) { + out += (getMidInfixToken(ia) -> cost) + minCost = cost + } } - Some(res) + if (0 < minCost) out += finalExpireCost + out.toList } } - val breakPenalty = if (isAfterOp) maxPrecedence - app.precedence else 1 - val expires = expiresOpt.fold(Seq(fullExpire -> 0)) { x => - (if (x.head._2 == 0) x else (fullExpire -> 0) +: x).reverse - } - val infixTooLong = infixSequenceLength(fullInfix) > style.newlines.afterInfixMaxCountPerExprForSome val breakMany = infixTooLong || afterInfix == Newlines.AfterInfix.many @@ -687,8 +679,8 @@ class FormatOps( def breakAfterComment(t: FT) = { val end = nextNonCommentSameLine(t) Policy ? end.right.isAny[T.LeftBrace, T.Comment] || { - if (end eq t) decideNewlinesOnlyAfterToken(end.left) - else decideNewlinesOnlyAfterClose(end.left) + if (end eq t) decideNewlinesOnlyAfterToken(end) + else decideNewlinesOnlyAfterClose(end) } } val nlMod = newStmtMod @@ -739,9 +731,9 @@ class FormatOps( val nlSplit = Split(nlMod, 0).andPolicy(breakAfterClose) .withIndent(nlIndent).withPolicy(nlPolicy) val singleLineSplit = Split(spaceMod, 0).notIf(noSingleLine) - .withSingleLine(endOfNextOp.getOrElse(closeFt).left) + .withSingleLine(endOfNextOp.getOrElse(closeFt)) .andPolicy(breakAfterClose) - .andPolicy(getSingleLineInfixPolicy(closeFt.left)) + .andPolicy(getSingleLineInfixPolicy(closeFt)) Seq(singleLineSplit, nlSplit) } @@ -756,7 +748,7 @@ class FormatOps( if (breakMany) TokenRanges.empty else insideBracesBlock(nextFT, expire, true) val ignore = exclude.isEmpty && singleLinePolicy.nonEmpty && - expire.end == fullExpire.end + (expire eq fullExpire) Split(ignore, cost)(ModExt(newStmtMod.getOrElse(spaceMod))) .withSingleLine(expire, exclude, noOptimal = cost != 0) } @@ -767,19 +759,19 @@ class FormatOps( } - def getSingleLineInfixPolicy(end: T) = Policy.on(end, prefix = "INFSLB") { - case Decision(t: FT, s) if isInfixOp(t.meta.leftOwner) => - SplitTag.InfixChainNoNL.activateOnly(s) - } + def getSingleLineInfixPolicy(end: FT) = Policy + .onLeft(end, prefix = "INFSLB") { + case Decision(t: FT, s) if isInfixOp(t.meta.leftOwner) => + SplitTag.InfixChainNoNL.activateOnly(s) + } - def getMidInfixToken(app: Member.Infix): T = { - val opToken = app.op.tokens.head - val opFollowsComment = tokens(opToken, -1).left.is[T.Comment] - if (opFollowsComment) getLastNonTrivialToken(app.lhs) else opToken + def getMidInfixToken(app: Member.Infix): FT = { + val opToken = getHead(app.op) + val beforeOp = prev(opToken) + val lhsLast = prevNonComment(beforeOp) + if (beforeOp eq lhsLast) opToken else lhsLast } - def getLastEnclosedToken(tree: Tree): T = getLastExceptParen(tree).left - @tailrec private def findNextInfixes( fullTree: Tree, @@ -851,11 +843,11 @@ class FormatOps( maxPrecedence } - def functionExpire(function: Term.FunctionTerm): (T, ExpiresOn) = function - .parent match { - case Some(SingleArgInBraces.OrBlock(_, _, e)) => e.left -> ExpiresOn.Before - case _ => getLastExceptParen(function).left -> ExpiresOn.After - } + def functionExpire(function: Term.FunctionTerm): (FT, ExpiresOn) = + function.parent match { + case Some(SingleArgInBraces.OrBlock(_, _, e)) => e -> ExpiresOn.Before + case _ => getLastExceptParen(function) -> ExpiresOn.After + } def mustForceConfigStyle(ft: FT)(implicit cfg: Newlines.ConfigStyleElement, @@ -900,9 +892,9 @@ class FormatOps( ft: FT, style: ScalafmtConfig, ): Seq[Split] = { - def getPolicy(expire: T) = expire match { - case lb: T.LeftBrace if template.self.tokens.isEmpty => - Policy.after(lb, "MLTMPL") { + def getPolicy(expire: FT) = expire.left match { + case lb: T.LeftBrace if template.body.selfOpt.isEmpty => + Policy.onRight(expire, "MLTMPL") { // Force template to be multiline. case d @ Decision(ftd @ FT(`lb`, right, _), _) if !right.is[T.RightBrace] && // corner case, body is {} @@ -915,7 +907,7 @@ class FormatOps( // this method is called on a `with` or comma; hence, it can // only refer to second or subsequent init/derive in a group // we'll indent only the second, but not any subsequent ones - val expire = x.getExpireToken.left + val expire = x.getExpireToken val indent = if (!x.isSecond) Indent.Empty else Indent(Num(indentIfSecond), expire, ExpiresOn.After) @@ -925,7 +917,7 @@ class FormatOps( Seq(Split(Space, 0).withIndent(indent), nlSplit(1)) else if (ft.hasBreak) Seq(nlSplit(0)) else { - val slbEnd = getLastToken(x.superType) + val slbEnd = getLast(x.superType) val exclude = insideBlock[T.LeftParen](ft, slbEnd) Seq( Split(Space, 0).withIndent(indent) @@ -938,11 +930,11 @@ class FormatOps( } } - def ctorWithChain(ownerSet: Set[Tree], lastToken: T)(implicit + def ctorWithChain(ownerSet: Set[Tree], lastFt: FT)(implicit style: ScalafmtConfig, ): Policy = Policy ? ((style.binPack.parentConstructors eq BinPack.ParentCtors.Always) || - ownerSet.isEmpty) || Policy.after(lastToken, prefix = "WITH") { + ownerSet.isEmpty) || Policy.onRight(lastFt, prefix = "WITH") { case d @ Decision(t @ FT(_, _: T.KwWith, _), _) if ownerSet.contains(t.meta.rightOwner) => d.onlyNewlinesWithoutFallback @@ -959,15 +951,14 @@ class FormatOps( indentLen: Int, extendsThenWith: => Boolean = false, )(implicit fileLine: FileLine, ft: FT, style: ScalafmtConfig): Seq[Split] = { - val lastToken = lastFt.left val nlMod = NewlineT(alt = Some(Space)) val indent = if (!isFirstCtor) Indent.Empty - else Indent(Num(indentLen), lastToken, ExpiresOn.After) + else Indent(Num(indentLen), lastFt, ExpiresOn.After) if (style.binPack.keepParentConstructors) if (ft.hasBreak) Seq(Split(nlMod, 0).withIndent(indent)) else { - val slbEnd = endOfSingleLineBlock(rhs.fold(lastFt)(getLast)) + val slbEnd = getSlbEndOnLeft(rhs.fold(lastFt)(getLast)) Seq( Split(Space, 0).withIndent(indent).withSingleLine( slbEnd, @@ -978,7 +969,7 @@ class FormatOps( ) } else if (isFirstCtor) { - val nlPolicy = ctorWithChain(owners, lastToken) + val nlPolicy = ctorWithChain(owners, lastFt) val nlOnelineTag = style.binPack.parentConstructors match { case BinPack.ParentCtors.Oneline => Right(true) case BinPack.ParentCtors.OnelineIfPrimaryOneline => @@ -988,19 +979,19 @@ class FormatOps( case _ => Right(style.newlines.fold) } val exclude = style.binPack.parentConstructors match { - case BinPack.ParentCtors.Always => insideBracesBlock(ft, lastToken, true) + case BinPack.ParentCtors.Always => insideBracesBlock(ft, lastFt, true) case _ => TokenRanges.empty } val noSyntaxNL = extendsThenWith - val pnlPolicy = PenalizeAllNewlines(lastToken, 1, noSyntaxNL = noSyntaxNL) - val slbEnd = endOfSingleLineBlock(lastFt) + val pnlPolicy = PenalizeAllNewlines(lastFt, 1, noSyntaxNL = noSyntaxNL) + val slbEnd = getSlbEndOnLeft(lastFt) Seq( Split(Space, 0) .withSingleLine(slbEnd, exclude = exclude, noSyntaxNL = noSyntaxNL) .orPolicy(pnlPolicy).withIndent(indent), Split(nlMod, 0).onlyIf(nlOnelineTag != Right(false)) .preActivateFor(nlOnelineTag.left.toOption) - .withSingleLineNoOptimal(lastToken, noSyntaxNL = noSyntaxNL) + .withSingleLineNoOptimal(lastFt, noSyntaxNL = noSyntaxNL) .withIndent(indent), Split(nlMod, 1).withPolicy(nlPolicy & pnlPolicy).withIndent(indent), ) @@ -1049,7 +1040,9 @@ class FormatOps( val lpOwner = ft.meta.leftOwner val FT(open, r, _) = ft - val close = matching(open).left + val nft = next(ft) + val close = matching(open) + val beforeClose = prev(close) val indentParam = Num(style.indent.getDefnSite(lpOwner)) val indentSep = Num((indentParam.n - 2).max(0)) val isBracket = open.is[T.LeftBracket] @@ -1079,7 +1072,7 @@ class FormatOps( // find the last param on the defn so that we can apply our `policy` // till the end. - val lastParens = allParenOwners.map(getLastToken) + val lastParens = allParenOwners.map(getLast) val lastParen = lastParens.lastOption.getOrElse(close) val shouldNotDangle = @@ -1100,13 +1093,13 @@ class FormatOps( // `lastParen` as well, which will be the same as the value param's // owner. - val paramGroupSplitter = Policy.on(lastParen, prefix = "VML") { + val beforeLastParen = if (shouldNotDangle) prev(lastParen) else null + val paramGroupSplitter = Policy.onLeft(lastParen, prefix = "VML") { // If this is a class, then don't dangle the last paren unless the line ends with a comment - case Decision(ftd @ FT(_, `lastParen`, _), _) - if shouldNotDangle && !isLeftCommentThenBreak(ftd) => - Seq(Split(NoSplit, 0)) + case Decision(`beforeLastParen`, _) + if !isLeftCommentThenBreak(beforeLastParen) => Seq(Split(NoSplit, 0)) // Indent separators `)(` and `](` by `indentSep` - case Decision(FT(_, `close`, _), _) => + case Decision(`beforeClose`, _) => Seq(Split(Newline, 0).withIndent(indentSep, close, ExpiresOn.After)) case Decision(FT(LeftParenOrBracket(), _, m), ss) if allParenOwners.contains(m.leftOwner) => @@ -1137,15 +1130,13 @@ class FormatOps( val space = Space(style.spaces.inParentheses) val slbEnd = if (style.newlines.sometimesBeforeColonInMethodReturnType) lastParen - else { - val afterLastParen = before(lastParen).right - if (afterLastParen.is[T.Colon]) afterLastParen else lastParen - } + else if (lastParen.right.is[T.Colon]) next(lastParen) + else lastParen val slbSplit = Split(space, 0).withSingleLine(slbEnd) .preActivateFor(SplitTag.VerticalMultilineSingleLine) if (isBracket) { - val noSlbPolicy = Policy.on(lastParen, prefix = "VML!SLB") { + val noSlbPolicy = Policy.onLeft(lastParen, prefix = "VML!SLB") { case Decision(FT(LeftParenOrBracket(), _, m), ss) if allParenOwners.contains(m.leftOwner) => ss.filter(!_.isActiveFor(SplitTag.VerticalMultilineSingleLine)) @@ -1154,11 +1145,10 @@ class FormatOps( if (allParenOwners.isEmpty) Split(space, 0).withSingleLine(close) else { val lpNext = getHead(allParenOwners.head) - val lpNextLeft = lpNext.left val slbPolicy = Policy ? isRightCommentThenBreak(lpNext) || - decideNewlinesOnlyAfterToken(lpNextLeft) + decideNewlinesOnlyAfterToken(lpNext) // If we can fit the type params, make it so - Split(space, 0).withSingleLine(lpNextLeft).orPolicy(slbPolicy) + Split(space, 0).withSingleLine(lpNext).orPolicy(slbPolicy) } val nlSplit = Split(Newline, 1, policy = policy).withIndent(firstIndent) Seq(slbSplit, noSplit.andPolicy(noSlbPolicy), nlSplit) @@ -1184,8 +1174,8 @@ class FormatOps( // If we can fit all in one block, make it so slbSplit.notIf(noSlb), Split(space, 0, policy = policy).onlyIf(spaceImplicit).andPolicy( - decideNewlinesOnlyAfterClose(r), - isRightCommentThenBreak(next(ft)), + decideNewlinesOnlyAfterClose(nft), + isRightCommentThenBreak(nft), ).withIndent(firstIndent), // Otherwise split vertically Split(nlMod, 1, policy = policy).withIndent(firstIndent), @@ -1261,7 +1251,6 @@ class FormatOps( def getFuncArrow(term: Term.FunctionTerm): Option[FT] = tokens .tokenBeforeOpt(term.body) .orElse(tokenAfterOpt(term.paramClause).map(getArrowAfter)) - .orElse(findFirst(getHead(term), term.pos.end)(_.left.is[T.RightArrow])) // look for arrow before body, if any, else after cond/pat def getCaseArrow(term: Case): FT = tokenBeforeOpt(term.body) @@ -1361,7 +1350,7 @@ class FormatOps( !cannotStartSelectChainOnExpr(thisSelectLike.qual) def checkParent = thisTree.parent match { case `nextSelect` => style.includeNoParensInSelectChains - case Some(p: Term.Apply) if getHeadToken(p.argClause).is[T.LeftBrace] => + case Some(p: Term.Apply) if getHead(p.argClause).left.is[T.LeftBrace] => style.includeCurlyBraceInSelectChains && !nextSelect.contains(lastApply) // exclude short curly case Some(p: Member.Apply) => p.fun eq thisTree case _ => false @@ -1407,7 +1396,7 @@ class FormatOps( if (style.xmlLiterals.assumeFormatted) { val end = matching(tok) val indent = Num(findXmlLastLineIndent(prev(end)), true) - splits.map(_.withIndent(indent, end.left, ExpiresOn.After)) + splits.map(_.withIndent(indent, end, ExpiresOn.After)) } else splits def withIndentOnXmlSpliceStart(ft: FT, splits: Seq[Split])(implicit @@ -1416,7 +1405,7 @@ class FormatOps( case t: T.Xml.SpliceStart if style.xmlLiterals.assumeFormatted => val end = matching(t) val indent = Num(findXmlLastLineIndent(prev(ft)), true) - splits.map(_.withIndent(indent, end.left, ExpiresOn.After)) + splits.map(_.withIndent(indent, end, ExpiresOn.After)) case _ => splits } @@ -1445,12 +1434,13 @@ class FormatOps( opens: List[FT], policy: Policy, ): Policy = opens.foldLeft(policy) { case (res, x) => - val endPos = nextNonComment(x).right match { - case t: T.LeftBrace => Policy.End > t - case t => Policy.End == t + val xft = nextNonComment(x) + val endPos = xft.right match { + case _: T.LeftBrace => Policy.End > xft + case _ => Policy.End >= xft } val onOpen = Policy(endPos, "NSTOPEN")(penalizeOpenNL) - Policy.End == x.left ==> onOpen ==> res + Policy.End <= x ==> onOpen ==> res } @tailrec @@ -1480,10 +1470,9 @@ class FormatOps( )(implicit style: ScalafmtConfig, ft: FT): Seq[Split] = { val btokens = body.tokens val blastFT = getLastNonTrivial(btokens, body) - val blast = blastFT.left - val expire = nextNonCommentSameLine(blastFT).left + val expire = nextNonCommentSameLine(blastFT) def penalize(penalty: Int) = Policy ? (penalty > 0) && - new PolicyOps.PenalizeAllNewlines(Policy.End == blast, penalty) + new PolicyOps.PenalizeAllNewlines(Policy.End <= blastFT, penalty) def getNlSplit(penalty: Int, nlCost: Int = 1)(implicit fileLine: FileLine, ): Split = nlSplitFunc(nlCost).andPolicy(penalize(penalty)) @@ -1492,30 +1481,30 @@ class FormatOps( spaceSplit.withIndents(spaceIndents), getNlSplit(1, nlCost)(spaceSplit.fileLine), ) - def getSlb(end: T, excl: TokenRanges)(implicit fileLine: FileLine) = + def getSlb(end: FT, excl: TokenRanges)(implicit fileLine: FileLine) = SingleLineBlock(end, exclude = excl, noSyntaxNL = true) def getSlbSplit( - end: T, + end: FT, exclude: TokenRanges = TokenRanges.empty, policy: Policy = Policy.NoPolicy, )(implicit fileLine: FileLine) = Split(Space, 0) .withPolicy(policy | getSlb(end, exclude)).withOptimalToken( end, killOnFail = exclude.isEmpty, - ignore = blast.start > end.start, + ignore = blastFT.idx > end.idx, ) def getSpaceSplit(penalty: Int, policy: Policy = Policy.NoPolicy)(implicit fileLine: FileLine, ) = { val spacePolicy = policy | penalize(penalty) - val miniSlbEnd = getSlbEndOnLeft(next(ft)).left + val miniSlbEnd = getSlbEndOnLeft(next(ft)) val slbLite = style.newlines.keep && (body.parent match { case Some(p: Term.Assign) => !p.parent.is[Term.ArgClause] || style.binPack.callSite == BinPack.Site.Never case _ => true }) - val opt = if (style.newlines.keep) miniSlbEnd else blast + val opt = if (style.newlines.keep) miniSlbEnd else blastFT Split(Space, 0).withSingleLineNoOptimal(miniSlbEnd) .andPolicy(spacePolicy) .withOptimalToken(opt, killOnFail = slbLite, recurseOnly = slbLite) @@ -1524,7 +1513,7 @@ class FormatOps( implicit fileLine: FileLine, ) = getSplits(getSpaceSplit(penalty, policy), nlCost) def getSlbSplits( - end: T = expire, + end: FT = expire, exclude: TokenRanges = TokenRanges.empty, policy: Policy = Policy.NoPolicy, )(implicit fileLine: FileLine) = ( @@ -1538,7 +1527,7 @@ class FormatOps( val thenBeg = getHead(t.thenp) val thenHasLB = thenBeg.left.is[T.LeftBrace] val end = if (thenHasLB) thenBeg else prevNonCommentBefore(thenBeg) - getSplits(getSlbSplit(end.left)) + getSplits(getSlbSplit(end)) case _: Term.If => getSlbSplits() case _: Term.TryClause => if (hasStateColumn) getSplits(getSpaceSplit(1)) else getSlbSplits() @@ -1555,7 +1544,7 @@ class FormatOps( } case b => tokenBeforeOpt(b) }).getOrElse(getLast(t)) - val end = endOfSingleLineBlock(afterYield) + val end = getSlbEndOnLeft(afterYield) getSlbSplits(end, exclude, penalize(1)) case None => getSlbSplits() } @@ -1566,8 +1555,8 @@ class FormatOps( if (callPolicy.nonEmpty) getPolicySplits(0, callPolicy) else if (isBodyEnclosedAsBlock(ia)) if (isKeep) getPolicySplits(0, Policy.NoPolicy) - else getSplits(getSlbSplit(getLastToken(lia.lhs))) - else getSplits(getSlbSplit(getLastToken(lia.op))) + else getSplits(getSlbSplit(getLast(lia.lhs))) + else getSplits(getSlbSplit(getLast(lia.op))) case b => val callPolicy = CallSite.getFoldedPolicy(b) val nlCost = b match { @@ -1594,8 +1583,8 @@ class FormatOps( private def unfoldedSpaceNonEmptyNonComment(body: Tree, slbOnly: Boolean)( implicit style: ScalafmtConfig, ): Split = { - val expire = nextNonCommentSameLine(getLastNonTrivial(body)).left - def slbSplit(end: T)(implicit fileLine: FileLine) = Split(Space, 0) + val expire = nextNonCommentSameLine(getLastNonTrivial(body)) + def slbSplit(end: FT)(implicit fileLine: FileLine) = Split(Space, 0) .withSingleLine(end, noSyntaxNL = true) getBlockStat(body) match { // we force newlines in for/yield @@ -1636,7 +1625,7 @@ class FormatOps( Seq(if (rhsIsCommentedOut(nextFt)) split.withNoIndent else split) } val policy = Policy - .on(nextFt.right, "CBCMT") { case Decision(`nextFt`, _) => splits } + .onRight(nextFt, "CBCMT") { case Decision(`nextFt`, _) => splits } Seq(Split(Space, 0, policy = policy)) } @@ -1700,7 +1689,7 @@ class FormatOps( val expire = nextNonCommentSameLine(rpOpt.fold(endFt) { rp => if (rp.left.end >= endFt.left.end) rp else endFt }) - nlSplit.withIndent(Num(style.indent.main), expire.left, ExpiresOn.After) + nlSplit.withIndent(Num(style.indent.main), expire, ExpiresOn.After) } def withIndent(nlSplit: Split, body: Tree, endFt: => FT)(implicit @@ -1720,17 +1709,18 @@ class FormatOps( } - def withNLPolicy(endFt: FT)(nlSplit: Split): Split = nlSplit - .andPolicy(nextNonCommentSameLine(endFt) match { - case FT(_, x: T.Keyword, _) => decideNewlinesOnlyBeforeToken(x) - case ft @ FT(_, x: T.Semicolon, _) => - val semiFt = nextNonCommentSameLineAfter(ft) - val semi = semiFt.left - if (semiFt.noBreak || (semi eq x) && !semiFt.right.is[T.Comment]) + def withNLPolicy(endFt: FT)(nlSplit: Split): Split = { + val nft = nextNonCommentSameLine(endFt) + nlSplit.andPolicy(nft.right match { + case _: T.Keyword => decideNewlinesOnlyBeforeToken(next(nft)) + case x: T.Semicolon => + val semi = nextNonCommentSameLineAfter(nft) + if (semi.noBreak || (semi.left eq x) && !semi.right.is[T.Comment]) decideNewlinesOnlyAfterToken(semi) else NoPolicy case _ => NoPolicy }) + } // Redundant () delims around case statements def getClosingIfCaseBodyEnclosedAsBlock(postArrowFt: FT, caseStat: CaseTree)( @@ -1786,20 +1776,20 @@ class FormatOps( bounds: Type.Param => Seq[Type], )(implicit style: ScalafmtConfig, ft: FT): Seq[Split] = { val boundOpt = bounds(tparam).find(_.pos.start > ft.right.end) - val expireOpt = boundOpt.map(getLastNonTrivialToken) + val expireOpt = boundOpt.map(getLastNonTrivial) getSplitsForTypeBounds(noNLMod, tparam, expireOpt) } def getSplitsForTypeBounds( noNLMod: => Modification, typeOwner: Tree, - boundEndOpt: Option[T], + boundEndOpt: Option[FT], )(implicit style: ScalafmtConfig, ft: FT): Seq[Split] = { - val typeEnd = getLastNonTrivialToken(typeOwner) + val typeEnd = getLastNonTrivial(typeOwner) val boundEnd = boundEndOpt.getOrElse(typeEnd) def indent = Indent(Num(style.indent.main), boundEnd, ExpiresOn.After) def unfoldPolicy = typeOwner match { - case tparam: Type.Param => Policy.on(typeEnd, prefix = "VB") { + case tparam: Type.Param => Policy.onLeft(typeEnd, prefix = "VB") { case Decision(t @ FT(_, _: T.Colon | _: T.Viewbound, _), s) if t.meta.rightOwner eq tparam => Decision.onlyNewlineSplits(s) } @@ -1885,30 +1875,29 @@ class FormatOps( val nonTrivialEnd = prevNonComment(end) val slbExpire = nextNonCommentSameLine(nonTrivialEnd) def head = getHead(treeTokens, tree) - val closeFT = (tree match { + val close = (tree match { case _: Member.Tuple => None case Term.Block((_: Member.Tuple) :: Nil) if !head.left.is[T.LeftBrace] => None case _ => tokens.getClosingIfWithinParens(nonTrivialEnd)(head).toOption .map(prevNonCommentSameLine) }).getOrElse(nextNonCommentSameLine(end)) - val close = closeFT.left def nlPolicy(implicit fileLine: FileLine) = Policy ? danglingKeyword && { - val couldBeTucked = closeFT.right.is[T.CloseDelim] && - closeFT.meta.rightOwner.is[Member.SyntaxValuesClause] + val couldBeTucked = close.right.is[T.CloseDelim] && + close.rightOwner.is[Member.SyntaxValuesClause] if (!couldBeTucked) decideNewlinesOnlyAfterClose(close) else decideNewlinesOnlyAfterToken(rank = 1, ifAny = true)(close) } val indentLen = indentOpt.getOrElse(style.indent.getSignificant) val indent = Indent(Num(indentLen), close, ExpiresOn.After) def nlOnly = forceNLIfTrailingStandaloneComments && - slbExpire.right.is[T.Comment] && slbExpire.right.start <= close.start + slbExpire.right.is[T.Comment] && slbExpire.idx < close.idx if (ft.hasBlankLine) Seq(Split(Newline2x, 0).withIndent(indent).withPolicy(nlPolicy)) else if (forceNL || nlOnly) Seq(Split(Newline, 0).withIndent(indent).withPolicy(nlPolicy)) else Seq( - Split(Space, 0).withSingleLine(slbExpire.left).withIndent(indent), + Split(Space, 0).withSingleLine(slbExpire).withIndent(indent), Split(Newline, 1).withIndent(indent).withPolicy(nlPolicy), ) } @@ -1956,7 +1945,6 @@ class FormatOps( def funcSplit(arg: Term.FunctionTerm)(implicit fl: FileLine) = { val end = getLast(arg) val opt = nextNonCommentSameLine(getFuncArrow(arg).getOrElse(end)) - .left Split(Space, 0).withSingleLine(opt) .andPolicy(decideNewlinesOnlyAfterToken(opt)) } @@ -2430,7 +2418,7 @@ class FormatOps( @tailrec private def isElsePWithOptionalBraces(tree: Term.If): Boolean = { val elsep = tree.elsep - !getHeadToken(elsep).is[T.LeftBrace] && + !getHead(elsep).left.is[T.LeftBrace] && (elsep match { case t: Term.If => isThenPWithOptionalBraces(t) || !ifWithoutElse(t) && isElsePWithOptionalBraces(t) @@ -2474,8 +2462,8 @@ class FormatOps( val beg = getOnOrBeforeOwned(hft, tree) val end = getLastNonTrivial(treeTokens, tree) - val kw = getClosingIfWithinParens(end)(beg).toOption.fold(end)(next).right - if (!kw.is[A]) return None + val kw = next(getClosingIfWithinParens(end)(beg).toOption.fold(end)(next)) + if (!kw.left.is[A]) return None val indent = style.indent.ctrlSite.getOrElse(style.indent.getSignificant) def policy = @@ -2659,17 +2647,6 @@ class FormatOps( case _ => other } - def getHeadToken(tree: Tree): T = getHead(tree).left - - def getLastToken(tree: Tree): T = getLast(tree).left - - def getLastTokenOpt(tree: Tree): Option[T] = getLastOpt(tree).map(_.left) - - def getLastNonTrivialToken(tree: Tree): T = getLastNonTrivial(tree).left - - def getLastNonTrivialTokenOpt(tree: Tree): Option[T] = tokens - .getLastNonTrivialOpt(tree).map(_.left) - def getEndOfBlock(ft: FT, parensToo: => Boolean)(implicit style: ScalafmtConfig, ): Option[FT] = ft.left match { @@ -2757,10 +2734,11 @@ class FormatOps( val closeBreak = dangleForTrailingCommas || ftBeforeClose.hasBreak def noNLPolicy(): Policy = { - val close = ftBeforeClose.right - if (scalaJsStyle) Policy(Policy.End == close, prefix = "tuckSJS") { - case Decision(`ftBeforeClose`, ss) => Decision.noNewlineSplits(ss) - } + def close = next(ftBeforeClose) + if (scalaJsStyle) + Policy(Policy.End >= ftBeforeClose, prefix = "tuckSJS") { + case Decision(`ftBeforeClose`, ss) => Decision.noNewlineSplits(ss) + } else style.newlines.source match { case Newlines.keep if closeBreak => decideNewlinesOnlyBeforeClose(close) case Newlines.fold @@ -2845,7 +2823,7 @@ class FormatOps( private def policyOnRightDelim( ft: FT, exclude: TokenRanges, - ): (Option[T], Policy) = { + ): (Option[FT], Policy) = { val beforeDelims = findTokenWith(ft, prev) { xft => noRighDelim(xft.left, xft) }.merge @@ -2860,8 +2838,7 @@ class FormatOps( @tailrec def iter(currft: FT): Option[Policy] = { val prevft = prevNonComment(currft) - val tok = prevft.left - val breakBeforeClose = matchingOpt(tok) match { + val breakBeforeClose = matchingOpt(prevft.left) match { case Some(open) => val cfg = styleMap.at(open) def cfgStyle = cfg.configStyleCallSite.prefer @@ -2875,7 +2852,7 @@ class FormatOps( } case _ => false } - if (breakBeforeClose) Some(decideNewlinesOnlyBeforeClose(tok)) + if (breakBeforeClose) Some(decideNewlinesOnlyBeforeClose(prevft)) else if (prevft eq beforeDelims) None else iter(prev(prevft)) } @@ -2883,26 +2860,25 @@ class FormatOps( iter(afterDelims) } - def policyWithDelay(policy: Policy) = { - val beforeDelimsEnd = beforeDelims.right.end + def policyWithDelay(policy: Policy) = // force break if multiline and if there's no other break - delayedBreakPolicy(Policy.End == beforeDelims.right, exclude) { + delayedBreakPolicy(Policy.End >= beforeDelims, exclude) { Policy.RelayOnSplit { case (s, nextft) => - s.isNL && nextft.right.end > beforeDelimsEnd // don't need anymore + s.isNL && nextft.idx > beforeDelims.idx // don't need anymore }(policy)(NoPolicy) } - } (afterDelims.right match { - case c: T.Dot => // check if Dot rule includes a break option - c -> GetSelectLike.onRightOpt(afterDelims).map { x => + case _: T.Dot => // check if Dot rule includes a break option + afterDelims -> GetSelectLike.onRightOpt(afterDelims).map { x => implicit val cfg = styleMap.at(afterDelims) cfg.newlines.getSelectChains match { case Newlines.classic => val (expireTree, nextSelect) = findLastApplyAndNextSelect(x.tree, cfg.encloseSelectChains) Right(canStartSelectChain(x, nextSelect, expireTree)) - case Newlines.keep => Left(Policy.on(c, "BP1L.NL") { + case Newlines.keep => + Left(Policy.onRight(afterDelims, "BP1L.NL") { case Decision(`afterDelims`, ss) => Decision .onlyNewlineSplits(ss) .map(_.preActivateFor(SplitTag.SelectChainBinPackNL)) @@ -2911,35 +2887,36 @@ class FormatOps( } }.getOrElse(Right(false)) case x @ LeftParenOrBracket() => - nextNonCommentSameLineAfter(afterDelims).right match { + val nft = nextNonCommentSameLineAfter(afterDelims) + nft.right match { case _: T.Comment => null - case c => // check if break would cause cfg style but not otherwise + case _ => // check if break would cause cfg style but not otherwise val cfg = styleMap.at(x) val ok = cfg.newlines.sourceIgnored || ! { cfg.configStyleCallSite.prefer && cfg.danglingParentheses.atCallSite(afterDelims.meta.rightOwner) } || next(afterDelims).hasBreak - c -> Right(ok) + nft -> Right(ok) } case _ => null }) match { case null => (None, NoPolicy) - case (c, policyOrOkToBreak) => + case (nft, policyOrOkToBreak) => val policyOpt = policyOrOkToBreak match { case Left(policy) => Some(policy) case Right(okToBreak) if !okToBreak => closeBreakPolicy() - case _ => Some(decideNewlinesOnlyBeforeToken(c)) + case _ => Some(decideNewlinesOnlyBeforeToken(next(nft))) } - (Some(c), policyOpt.fold(Policy.noPolicy)(policyWithDelay)) + (Some(nft), policyOpt.fold(Policy.noPolicy)(policyWithDelay)) } } - def getPolicy(isCallSite: Boolean, exclude: TokenRanges)( - afterArg: FT, - )(implicit fileLine: FileLine): (Option[T], Policy) = afterArg.right match { - case c: T.Comma // check for trailing comma, which needs no breaks + def getPolicy(isCallSite: Boolean, exclude: TokenRanges)(afterArg: FT)( + implicit fileLine: FileLine, + ): (Option[FT], Policy) = afterArg.right match { + case _: T.Comma // check for trailing comma, which needs no breaks if !nextNonCommentAfter(afterArg).right.is[T.CloseDelim] => - (None, splitOneArgPerLineAfterCommaOnBreak(exclude)(c)) + (None, splitOneArgPerLineAfterCommaOnBreak(exclude)(next(afterArg))) case _: T.CloseDelim if isCallSite => policyOnRightDelim(afterArg, exclude) case _ => (None, NoPolicy) } @@ -2964,7 +2941,7 @@ class FormatOps( // check if all of them can be converted to parens val nft = if (isWithinBraces) ft else next(ft) def getTokenRanges = { - val tr = insideBracesBlock(nft, rb.left) + val tr = insideBracesBlock(nft, rb) .filter(x => !couldHaveBracesConvertedToParens(x.lt.leftOwner)) if (tr.ranges.exists(!_.lt.left.is[T.LeftBrace])) null else Some(tr) } @@ -2988,15 +2965,13 @@ class FormatOps( ): (Modification, Option[TokenRanges]) = { val tr = insideBracesBlockIfBracesToParens(rb, mod, isWithinBraces) if ((tr eq null) || tr.isEmpty) (mod, tr) - else (SpaceOrNoSplit(Policy.End < rb.left), tr) + else (SpaceOrNoSplit(Policy.End < rb), tr) } } object FormatOps { - class SelectLike(val tree: Term, val qual: Term, val nameFt: FT) { - def nameToken: T = nameFt.left - } + class SelectLike(val tree: Term, val qual: Term, val nameFt: FT) {} object SelectLike { def apply(tree: Term.Select)(implicit ftoks: FormatTokens): SelectLike = @@ -3026,7 +3001,7 @@ object FormatOps { } def getOpenParenAlignIndents( - end: T, + end: FT, )(implicit style: ScalafmtConfig): Seq[Indent] = if (style.align.closeParenSite) Seq( Indent(Length.StateColumn, end, ExpiresOn.After), diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala index c93237ae8..15bdc646f 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala @@ -188,6 +188,9 @@ class FormatTokens(leftTok2tok: Map[TokenHash, Int])(val arr: Array[FT]) final def nextNonCommentSameLineAfter(curr: FT): FT = nextNonCommentSameLine(next(curr)) + final def nextAfterNonCommentSameLine(curr: FT): FT = + next(nextNonCommentSameLine(curr)) + final def nextNonComment(curr: FT): FT = findToken(curr, next)(!_.right.is[T.Comment]) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala index 8adc90c0c..f009accba 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala @@ -5,17 +5,18 @@ import scala.meta.tokens.{Token => T} import scala.annotation.tailrec sealed abstract class ExpiresOn { - def notExpiredBy(ft: FT, expireEnd: Int): Boolean + def notExpiredBy(ft: FT, idxBeforeExpire: Int): Boolean } object ExpiresOn { case object After extends ExpiresOn { - def notExpiredBy(ft: FT, expireEnd: Int): Boolean = ft.right.start < - expireEnd + def notExpiredBy(ft: FT, idxBeforeExpire: Int): Boolean = ft.idx <= + idxBeforeExpire } case object Before extends ExpiresOn { - def notExpiredBy(ft: FT, expireEnd: Int): Boolean = ft.right.end < expireEnd + def notExpiredBy(ft: FT, idxBeforeExpire: Int): Boolean = ft.idx < + idxBeforeExpire } @inline @@ -50,12 +51,13 @@ object Length { case class ActualIndent( length: Int, - expireEnd: Int, + expire: FT, expiresAt: ExpiresOn, reset: Boolean, ) { + private val idxBeforeExpire = expire.idx - 1 @inline - def notExpiredBy(ft: FT): Boolean = expiresAt.notExpiredBy(ft, expireEnd) + def notExpiredBy(ft: FT): Boolean = expiresAt.notExpiredBy(ft, idxBeforeExpire) } abstract class Indent { @@ -80,27 +82,22 @@ abstract class Indent { * If Right, then expires when [[expire]] is curr.right, otherwise curr.left * in [[BestFirstSearch]]. */ -private class IndentImpl(length: Length, expire: T, expiresAt: ExpiresOn) +private class IndentImpl(length: Length, expire: FT, expiresAt: ExpiresOn) extends Indent { override def hasStateColumn: Boolean = length eq Length.StateColumn override def switch(trigger: T, on: Boolean): Indent = this override def withStateOffset(offset: Int): Option[ActualIndent] = Some( - ActualIndent( - length.withStateOffset(offset), - expire.end, - expiresAt, - length.reset, - ), + ActualIndent(length.withStateOffset(offset), expire, expiresAt, length.reset), ) override def toString: String = { val when = if (expiresAt == ExpiresOn.Before) '<' else '>' - s"$length$when$expire:${expire.end}" + s"$length$when${expire.left}[${expire.idx}]" } } object Indent { - def apply(length: Length, expire: => T, expiresAt: => ExpiresOn): Indent = + def apply(length: Length, expire: => FT, expiresAt: => ExpiresOn): Indent = length match { case Length.Num(0, _) => Empty case x => new IndentImpl(x, expire, expiresAt) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala index bfcf9d799..cbbe9ce54 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala @@ -15,7 +15,7 @@ case class ModExt(mod: Modification, indents: Seq[Indent] = Seq.empty) { @inline def isNL: Boolean = mod.isNL - def withIndent(length: => Length, expire: => T, when: ExpiresOn): ModExt = + def withIndent(length: => Length, expire: => FT, when: ExpiresOn): ModExt = length match { case Length.Num(0, _) => this case x => withIndentImpl(Indent(x, expire, when)) @@ -23,7 +23,7 @@ case class ModExt(mod: Modification, indents: Seq[Indent] = Seq.empty) { def withIndentOpt( length: => Length, - expire: Option[T], + expire: Option[FT], when: ExpiresOn, ): ModExt = expire.fold(this)(withIndent(length, _, when)) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala index 32e4bd708..71caff3b3 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala @@ -1,5 +1,7 @@ package org.scalafmt.internal +import org.scalafmt.util.LoggerOps + import org.scalameta.FileLine import scala.meta.tokens.{Token => T} @@ -84,26 +86,37 @@ object Policy { )(f: Pf)(implicit fileLine: FileLine): Policy = new ClauseImpl(f, endPolicy, prefix, noDequeue, rank) - def after(token: T, prefix: String, noDequeue: Boolean = false, rank: Int = 0)( - f: Pf, - )(implicit fileLine: FileLine): Policy = - apply(End > token, prefix, noDequeue, rank)(f) - - def before(token: T, prefix: String, noDequeue: Boolean = false, rank: Int = 0)( - f: Pf, - )(implicit fileLine: FileLine): Policy = - apply(End < token, prefix, noDequeue, rank)(f) - def after(trigger: T, policy: Policy)(implicit fileLine: FileLine): Policy = new Switch(NoPolicy, trigger, policy) def before(policy: Policy, trigger: T)(implicit fileLine: FileLine): Policy = new Switch(policy, trigger, NoPolicy) - def on(token: T, prefix: String, noDequeue: Boolean = false, rank: Int = 0)( + def beforeLeft( + exp: FT, + prefix: String, + noDequeue: Boolean = false, + rank: Int = 0, + )(f: Pf)(implicit fileLine: FileLine): Policy = + apply(End < exp, prefix, noDequeue, rank)(f) + + def onLeft(exp: FT, prefix: String, noDequeue: Boolean = false, rank: Int = 0)( f: Pf, )(implicit fileLine: FileLine): Policy = - apply(End == token, prefix, noDequeue, rank = rank)(f) + apply(End <= exp, prefix, noDequeue, rank = rank)(f) + + def onRight(exp: FT, prefix: String, noDequeue: Boolean = false, rank: Int = 0)( + f: Pf, + )(implicit fileLine: FileLine): Policy = + apply(End >= exp, prefix, noDequeue, rank = rank)(f) + + def afterRight( + exp: FT, + prefix: String, + noDequeue: Boolean = false, + rank: Int = 0, + )(f: Pf)(implicit fileLine: FileLine): Policy = + apply(End > exp, prefix, noDequeue, rank)(f) abstract class Clause(implicit val fileLine: FileLine) extends Policy { val endPolicy: End.WithPos @@ -414,39 +427,55 @@ object Policy { } } - sealed trait End extends (T => End.WithPos) { - def apply(token: T): End.WithPos + sealed trait End extends (FT => End.WithPos) { + def apply(exp: FT): End.WithPos } object End { - def <(token: T): End.WithPos = Before(token) - def >(token: T): End.WithPos = After(token) - def ==(token: T): End.WithPos = On(token) + def <(exp: FT): End.WithPos = BeforeLeft(exp) + def <=(exp: FT): End.WithPos = OnLeft(exp) + def >=(exp: FT): End.WithPos = OnRight(exp) + def >(exp: FT): End.WithPos = AfterRight(exp) + + @inline + private def getDescWithoutPos(token: T): String = LoggerOps + .tokWithoutPos(token) sealed trait WithPos { - def notExpiredBy(ft: FT): Boolean + protected val endIdx: Int + def notExpiredBy(ft: FT): Boolean = ft.idx <= endIdx def ==>(policy: Policy): Policy = if (policy.isEmpty) NoPolicy else new Policy.Delay(policy, this) } - case object After extends End { - def apply(token: T): WithPos = new End.WithPos { - def notExpiredBy(ft: FT): Boolean = ft.left.start <= token.start - override def toString: String = s">${token.structure}" + case object BeforeLeft extends End { + def apply(exp: FT): WithPos = new End.WithPos { + protected val endIdx: Int = exp.idx - 2 + override def toString: String = + s"<${getDescWithoutPos(exp.left)}[${exp.idx}]" + } + } + case object OnLeft extends End { + def apply(exp: FT): WithPos = new End.WithPos { + protected val endIdx: Int = exp.idx - 1 + override def toString: String = + s"<=${getDescWithoutPos(exp.left)}[${exp.idx}]" } } - case object Before extends End { - def apply(token: T): WithPos = new End.WithPos { - def notExpiredBy(ft: FT): Boolean = ft.right.start < token.start - override def toString: String = s"<${token.structure}" + case object OnRight extends End { + def apply(exp: FT): WithPos = new End.WithPos { + protected val endIdx: Int = exp.idx + override def toString: String = + s">=${getDescWithoutPos(exp.left)}[${exp.idx}]" } } - case object On extends End { - def apply(token: T): WithPos = new End.WithPos { - def notExpiredBy(ft: FT): Boolean = ft.right.start <= token.start - override def toString: String = s"@${token.structure}" + case object AfterRight extends End { + def apply(exp: FT): WithPos = new End.WithPos { + protected val endIdx: Int = exp.idx + 1 + override def toString: String = + s">${getDescWithoutPos(exp.left)}[${exp.idx}]" } } case object Never extends WithPos { - override def notExpiredBy(ft: FT): Boolean = true + override protected val endIdx: Int = Int.MaxValue } } 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 746a2f906..facd83258 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 @@ -75,11 +75,11 @@ class Router(formatOps: FormatOps) { case FT(_: T.BOF, _, _) => Seq(Split(NoSplit, 0)) case FT(_: T.Shebang, _, _) => Seq(Split(Newline2x(ft), 0)) case FT(start: T.Interpolation.Start, _, m) => - val end = matching(start).left + val end = matching(start) val policy = { val penalty = BreakSingleLineInterpolatedString if (style.newlines.inInterpolation eq Newlines.InInterpolation.avoid) - Policy.on(end, "INTERP-AVOID-NL", rank = -1) { + Policy.onLeft(end, "INTERP-AVOID-NL", rank = -1) { case Decision(_, ss) => ss.map { s => if (s.isNL) s.withPenalty(penalty) else if (s.optimalAt.isEmpty) s @@ -87,14 +87,14 @@ class Router(formatOps: FormatOps) { } } else if (!style.newlines.sourceIgnored && !isTripleQuote(m.left.text)) - Policy.on(end, "INTERP-KEEP-NONL") { + Policy.onLeft(end, "INTERP-KEEP-NONL") { case Decision(x, ss) if x.noBreak => ss.map(s => if (s.isNL) s.withPenalty(penalty) else s) } else Policy ? (style.newlines.inInterpolation eq Newlines.InInterpolation.allow) && - Policy.on(end, "INTERP-ALLOW-NL", rank = -1) { + Policy.onLeft(end, "INTERP-ALLOW-NL", rank = -1) { case Decision(_, ss) => ss .map(s => if (s.isNL) s.withPenalty(1) else s) } @@ -140,9 +140,8 @@ class Router(formatOps: FormatOps) { // Import left brace case FT(open: T.LeftBrace, _, _) if existsParentOfType[ImportExportStat](leftOwner) => - val closeFt = matching(open) - val close = closeFt.left - val beforeClose = prev(closeFt) + val close = matching(open) + val beforeClose = prev(close) val policy = SingleLineBlock( close, okSLC = style.importSelectors eq ImportSelectors.singleLine, @@ -174,24 +173,22 @@ class Router(formatOps: FormatOps) { // Interpolated string left brace case FT(open @ T.LeftBrace(), _, _) if prev(ft).left.is[T.Interpolation.SpliceStart] => - val closeFt = matching(open) - val close = closeFt.left + val close = matching(open) val alignIndents = - if (style.align.inInterpolation) Some { - Seq( - Indent(Length.StateColumn, close, ExpiresOn.After), - Indent(Length.Num(style.indent.main), close, ExpiresOn.Before), - Indent(Length.Num(-1), close, ExpiresOn.After), - ) - } - else None + if (style.align.inInterpolation) Seq( + Indent(Length.StateColumn, close, ExpiresOn.After), + Indent(Length.Num(style.indent.main), close, ExpiresOn.Before), + Indent(Length.Num(-1), close, ExpiresOn.After), + ) + else Nil def spaceSplit(implicit fileLine: FileLine) = Split(Space(style.spaces.inInterpolatedStringCurlyBraces), 0) - .withIndents(alignIndents.getOrElse(Nil)) + .withIndents(alignIndents) def newlineSplit(cost: Int)(implicit fileLine: FileLine) = { - def mainIndents = - Seq(Indent(style.indent.main, close, ExpiresOn.Before)) - Split(Newline, cost).withIndents(alignIndents.getOrElse(mainIndents)) + val mainIndents = + if (alignIndents.nonEmpty) alignIndents + else Seq(Indent(style.indent.main, close, ExpiresOn.Before)) + Split(Newline, cost).withIndents(mainIndents) .withPolicy(decideNewlinesOnlyBeforeClose(close)) } def isSimpleInterpolate = !(leftOwner match { @@ -214,11 +211,10 @@ class Router(formatOps: FormatOps) { * - 2 Interpolation.Part (next string) * - 3 Interpolation.End (quotes) or Interpolation.SliceStart/LBrace (${) */ - val afterClose = tokens(closeFt, 3) + val afterClose = tokens(close, 3) val lastPart = afterClose.left.is[T.Interpolation.End] - val slbEnd = endOfSingleLineBlock( - if (lastPart) afterClose else next(afterClose), - ) + val slbEnd = + getSlbEndOnLeft(if (lastPart) afterClose else next(afterClose)) Seq(spaceSplit.withSingleLine(slbEnd), newlineSplit(1)) } @@ -251,8 +247,7 @@ class Router(formatOps: FormatOps) { // { ... } Blocks case FT(open: T.LeftBrace, right, _) => - val closeFT = matching(open) - val close = closeFT.left + val close = matching(open) val isSelfAnnotationNL = style.optIn.selfAnnotationNewline && (hasBreak() || style.newlines.sourceIgnored) && (leftOwner match { // Self type: trait foo { self => ... } @@ -321,20 +316,19 @@ class Router(formatOps: FormatOps) { val noLambdaSplit = style.newlines.keepBreak(newlines) || lambdaArrow == null || !lambdaNLOnly.contains(false) val lambdaExpire = - if (lambdaArrow eq null) null - else nextNonCommentSameLine(lambdaArrow).left + if (lambdaArrow eq null) null else nextNonCommentSameLine(lambdaArrow) def getSingleLinePolicy = { - val needBreak = closeFT.right match { - case _: T.KwElse => closeFT.meta.rightOwner match { + val needBreak = close.right match { + case _: T.KwElse => close.rightOwner match { case p: Term.If => p.thenp eq leftOwner case _ => false } - case _: T.KwCatch => closeFT.meta.rightOwner match { + case _: T.KwCatch => close.rightOwner match { case p: Term.TryClause => p.expr eq leftOwner case _ => false } - case _: T.KwFinally => closeFT.meta.rightOwner match { + case _: T.KwFinally => close.rightOwner match { case p: Term.TryClause => (p.expr eq leftOwner) || p.catchClause.contains(leftOwner) case _ => false @@ -386,7 +380,7 @@ class Router(formatOps: FormatOps) { val noSplitMod = xmlSpace(leftOwner) val (slbMod, slbParensExclude) = if (singleLineDecisionOpt.isEmpty) (noSplitMod, None) - else getBracesToParensMod(closeFT, noSplitMod, isWithinBraces = true) + else getBracesToParensMod(close, noSplitMod, isWithinBraces = true) val singleLineSplitOpt = { if (slbParensExclude eq null) None else singleLineDecisionOpt }.map { sld => @@ -394,23 +388,24 @@ class Router(formatOps: FormatOps) { val expire = leftOwner.parent match { case Some(p: Term.ForYield) if !sld && sldPolicy.isEmpty && style.newlines.fold && - leftOwner.is[Term.EnumeratorsBlock] => getLastToken(p) + leftOwner.is[Term.EnumeratorsBlock] => getLast(p) case _ if style.newlines.isBeforeOpenParenCallSite => close - case _ => endOfSingleLineBlock(closeFT) + case _ => getSlbEndOnLeft(close) } // copy logic from `( ...`, binpack=never, defining `slbSplit` val slbParensPolicy = Policy ? (slbMod eq noSplitMod) || { - val beforeClose = prev(closeFT) - Policy.End < close ==> Policy.on(close, "BracesToParens") { - case Decision(`beforeClose`, ss) => ss - .flatMap(s => if (s.isNL) None else Some(s.withMod(NoSplit))) - } + val beforeClose = prev(close) + Policy.End <= beforeClose ==> + Policy.onRight(beforeClose, "BracesToParens") { + case Decision(`beforeClose`, ss) => ss + .flatMap(s => if (s.isNL) None else Some(s.withMod(NoSplit))) + } } val exclude = slbParensExclude.getOrElse(TokenRanges.empty) val slbPolicy = if (exclude.isEmpty) slbParensPolicy else Policy.RelayOnSplit((s, _) => s.isNL)(slbParensPolicy)( - Policy.on(close, "BracesToParensFailed") { case _ => Nil }, + Policy.onLeft(close, "BracesToParensFailed") { case _ => Nil }, ) Split(slbMod, 0).withSingleLine( expire, @@ -490,8 +485,8 @@ class Router(formatOps: FormatOps) { val (mod, policy) = singleLineSplitOpt match { case Some(slbSplit) if singleLineSplit.isIgnored => val arrSplit = slbSplit.withMod(Space) - slbMod -> Policy.after(lambdaExpire, s"FNARR($arrSplit)") { - case Decision(FT(`lambdaExpire`, _, _), ss) => + slbMod -> Policy.onRight(lambdaExpire, s"FNARR($arrSplit)") { + case Decision(`lambdaExpire`, ss) => var hadNoSplit = false val nlSplits = ss.flatMap { s => // penalize NL one extra, for closing brace @@ -533,7 +528,6 @@ class Router(formatOps: FormatOps) { style.newlines.fold || !rightOwner.is[Defn] } => val exp = nextNonCommentSameLine(getLastNonTrivial(leftFunc.body)) - .left spaceSplitBase.withSingleLine(exp, noSyntaxNL = true) case _ => Split.ignored } @@ -552,7 +546,7 @@ class Router(formatOps: FormatOps) { }) => val (endOfFunction, expiresOn) = leftOwner match { case t: Term.FunctionTerm => functionExpire(t) - case t => getLastNonTrivialToken(t) -> ExpiresOn.Before + case t => getLastNonTrivial(t) -> ExpiresOn.Before } val indent = // don't indent if the body is empty `{ x => }` @@ -597,7 +591,7 @@ class Router(formatOps: FormatOps) { // 2020-01: break after same-line comments, and any open brace val nonComment = nextNonCommentSameLine(ft) val hasBlock = nonComment.right.is[T.LeftBrace] && - (matching(nonComment.right).left eq endOfFunction) + (matching(nonComment.right) eq endOfFunction) val noSplit = if (!hasBlock && (nonComment eq ft)) Split(noSingleLine, 0)(Space) .withSingleLine(endOfFunction) @@ -607,7 +601,7 @@ class Router(formatOps: FormatOps) { Split(Space, 0) .withIndent(indent, endOfFunction, expiresOn, hasBlock) .withOptimalToken( - nextNonCommentSameLineAfter(nonComment).left, + nextNonCommentSameLineAfter(nonComment), killOnFail = false, ) Seq(noSplit, newlineSplit(1 + nestedApplies(leftOwner))) @@ -628,7 +622,7 @@ class Router(formatOps: FormatOps) { Split(Newline2x(ft), cost) CtrlBodySplits.checkComment(nlSplit(ft)) { nft => def withSlbSplit(implicit l: FileLine) = Seq( - baseSplit.withSingleLine(getLastNonTrivialToken(body)), + baseSplit.withSingleLine(getLastNonTrivial(body)), nlSplit(nft)(1), ) implicit val beforeMultiline = style.newlines.getBeforeMultiline @@ -668,7 +662,7 @@ class Router(formatOps: FormatOps) { } Seq( Split(noSpace, 0)(Space) - .withSingleLine(endOfSingleLineBlock(getLast(stmt))), + .withSingleLine(getSlbEndOnLeft(getLast(stmt))), // For some reason, this newline cannot cost 1. Split(Newline2x(ft), 0), ) @@ -701,12 +695,12 @@ class Router(formatOps: FormatOps) { case Tree.WithBody(body) => tokenBeforeOpt(body).map { x => val y = nextNonCommentSameLine(x) val ok = (x ne y) && y.noBreak && y.right.is[T.LeftBrace] - if (ok) y.right else y.left + if (ok) next(y) else y } case t: Mod.Annot if !style.newlines.keep => - getLastOpt(t).map(endOfSingleLineBlock) + getLastOpt(t).map(getSlbEndOnLeft) case _ => None - }).getOrElse(right) + }).getOrElse(next(ft)) Seq( // This split needs to have an optimalAt field. Split(Space, 0).onlyIf(spaceCouldBeOk).withSingleLine(expire), @@ -765,18 +759,16 @@ class Router(formatOps: FormatOps) { indentLen: Int, shouldAlignBefore: Align => Boolean, )(lastSyntaxClause: => Option[Member.SyntaxValuesClause]) = { - val closeFt = matching(open) - val close = closeFt.left + val close = matching(open) val indent = Indent(indentLen, close, ExpiresOn.After) val isAlignFirstParen = shouldAlignBefore(style.align) && !prevNonComment(ft).left.is[T.RightParen] def noSplitSplit(implicit fileLine: FileLine) = if (isAlignFirstParen) baseNoSplit else baseNoSplit.withSingleLine(close) - def afterClose: Option[T] = { - val ftAfterClose = nextNonComment(closeFt) - val tokAfterClose = ftAfterClose.right - val matches = tokAfterClose match { + def afterClose: Option[FT] = { + val ftAfterClose = nextNonComment(close) + val matches = ftAfterClose.right match { case _: T.LeftParen => true case _: T.Colon if defn => ftAfterClose.left.is[T.Comment] || @@ -784,19 +776,18 @@ class Router(formatOps: FormatOps) { colonDeclType(ftAfterClose.meta.rightOwner).isDefined case _ => false } - if (matches) Some(tokAfterClose) else None + if (matches) Some(next(ftAfterClose)) else None } val splits = src match { case Newlines.unfold => val rightParent = rightOwner.parent.get val slbEnd = - if (defn) beforeDefRhs - .fold(getLastToken(rightParent))(prevNonComment(_).left) - else getLastToken(getLastCall(rightParent)) + if (defn) beforeDefRhs.fold(getLast(rightParent))(prevNonComment) + else getLast(getLastCall(rightParent)) Seq( baseNoSplit.withSingleLine(slbEnd), Split(Newline, 1).withIndent(indent).withPolicy( - penalizeNewlineByNesting(open, close), + penalizeNewlineByNesting(ft, close), isSeqMulti(getArgs(ft.meta.rightOwner)), ).andPolicy(afterClose.map(decideNewlinesOnlyBeforeClose)), ) @@ -804,8 +795,9 @@ class Router(formatOps: FormatOps) { if (hasBreak()) Seq(Split(Newline, 0).withIndent(indent)) else Seq(noSplitSplit, Split(Newline, 1).withIndent(indent)) case _ => - val nlColonPolicy = afterClose.map { - case t: T.Colon => decideNewlinesOnlyBeforeClose(t) + val nlColonPolicy = afterClose match { + case Some(x @ FT(_: T.Colon, _, _)) => + decideNewlinesOnlyBeforeClose(x) case _ => NoPolicy } Seq( @@ -814,7 +806,7 @@ class Router(formatOps: FormatOps) { ) } val argsOpt = if (isAlignFirstParen) lastSyntaxClause else None - argsOpt.flatMap(getLastTokenOpt).fold(splits) { x => + argsOpt.flatMap(getLastOpt).fold(splits) { x => val noSplitIndents = Seq( Indent(StateColumn, x, ExpiresOn.Before), Indent(-indentLen, x, ExpiresOn.Before), @@ -869,8 +861,8 @@ class Router(formatOps: FormatOps) { ) => val policy = Policy ? (style.binPack.keepParentConstructors || template.pos.isEmpty) || { - val expire = templateDerivesOrCurlyOrLastNonTrivial(template).left - val forceNewlineBeforeExtends = Policy.before(expire, "NLPCTOR") { + val expire = templateDerivesOrCurlyOrLastNonTrivial(template) + val forceNewlineBeforeExtends = Policy.beforeLeft(expire, "NLPCTOR") { case Decision(FT(_, soft.ExtendsOrDerives(), m), s) if m.rightOwner eq template => s.filter(x => x.isNL && !x.isActiveFor(SplitTag.OnelineWithChain)) @@ -885,10 +877,10 @@ class Router(formatOps: FormatOps) { case FT(open @ LeftParenOrBracket(), _, _) if ft.meta.formatOff && leftOwner.isAny[Member.SyntaxValuesClause, Member.Tuple] => - val close = matching(open).left + val close = matching(open) def splits(xft: FT, policy: Policy)(implicit l: FileLine) = Seq(Split(Provided(xft), 0, policy = policy)) - val policy = Policy.on(close, "(FMT:OFF)", rank = Int.MaxValue) { + val policy = Policy.onLeft(close, "(FMT:OFF)", rank = Int.MaxValue) { case Decision(xft, _) => splits(xft, NoPolicy) } splits(ft, policy) @@ -901,39 +893,40 @@ class Router(formatOps: FormatOps) { // Term.Apply and friends case FT(lp: T.LeftParen, _, LambdaAtSingleArgCallSite(lambda)) => - val closeFt = matching(lp) - val close = closeFt.left + val close = matching(lp) + val beforeClose = prev(close) val newlinePolicy = Policy ? style.danglingParentheses.callSite && decideNewlinesOnlyBeforeClose(close) val noSplitMod = if ( style.newlines.alwaysBeforeCurlyLambdaParams || - getMustDangleForTrailingCommas(prev(closeFt)) + getMustDangleForTrailingCommas(beforeClose) ) null else getNoSplitAfterOpening(ft, commentNL = null) def multilineSpaceSplit(implicit fileLine: FileLine): Split = { - val lambdaLeft: Option[T] = - matchingOpt(functionExpire(lambda)._1) match { - case Some(FT(lb: T.LeftBrace, _, _)) => Some(lb) + val lambdaLeft: Option[FT] = + matchingOpt(functionExpire(lambda)._1.left) match { + case x @ Some(FT(_: T.LeftBrace, _, _)) => x case _ => None } val arrowFt = getFuncArrow(lambda).get - val lambdaIsABlock = lambdaLeft.contains(arrowFt.right) + val lambdaIsABlock = lambdaLeft.exists(_.left eq arrowFt.right) val lambdaToken = nextNonCommentSameLine( if (lambdaIsABlock) next(arrowFt) else arrowFt, - ).left + ) val spacePolicy = SingleLineBlock(lambdaToken) ==> { - def before = Policy.End < close ==> Policy.on(close, "NODANGLE") { - case Decision(FT(bc, `close`, _), _) => + def before = Policy.End < close ==> Policy.onLeft(close, "NODANGLE") { + case Decision(`beforeClose`, _) => + val bc = beforeClose.left if (bc.is[T.Comment]) if (bc.text.startsWith("//")) Nil else Seq(Split(Space, 0)) else Seq(Split(Space(style.spaces.inParentheses), 0)) } Policy ? lambdaIsABlock || - Policy.RelayOnSplit.by(Policy.End == lambdaLeft.getOrElse(close))( + Policy.RelayOnSplit.by(Policy.End <= lambdaLeft.getOrElse(close))( (s, _) => s.isNL, )(before)(newlinePolicy) } @@ -967,9 +960,9 @@ class Router(formatOps: FormatOps) { else style.binPack.defnSiteFor(open) == BinPack.Site.Never && isParamClauseSite(leftOwner) } => - val afterClose = matching(open) - val close = afterClose.left - val beforeClose = prev(afterClose) + val afterOpen = next(ft) + val close = matching(open) + val beforeClose = prev(close) val tupleSite = isTuple(leftOwner) val anyDefnSite = isParamClauseSite(leftOwner) val defnSite = !tupleSite && anyDefnSite @@ -1031,15 +1024,14 @@ class Router(formatOps: FormatOps) { val isBeforeOpenParen = if (defnSite) style.newlines.isBeforeOpenParenDefnSite else style.newlines.isBeforeOpenParenCallSite - val optimalFtOpt = + val optimalOpt = if (isBeforeOpenParen || !defnSite || isBracket) None else defnSiteLastToken(leftOwner) - val optimalFt: FT = getSlbEndOnLeft(optimalFtOpt.getOrElse(afterClose)) - val optimal = optimalFt.left + val optimal: FT = getSlbEndOnLeft(optimalOpt.getOrElse(close)) val wouldDangle = onlyConfigStyle || mustDangleForTrailingCommas || dangleCloseDelim || closeBreak && beforeClose.left.is[T.Comment] - val optimalIsComment = optimal.is[T.Comment] + val optimalIsComment = optimal.left.is[T.Comment] val newlinePolicy: Policy = Policy ? (wouldDangle || optimalIsComment) && @@ -1073,7 +1065,7 @@ class Router(formatOps: FormatOps) { val breakToken = nextNonCommentSameLine(assign.rhs match { case b: Term.Block if isEnclosedInBraces(b) => getHead(b) case x => tokenBefore(x) - }).left + }) val newlineAfterAssignDecision = Policy ? newlinePolicy.isEmpty || decideNewlinesOnlyAfterToken(breakToken) Seq( @@ -1099,8 +1091,7 @@ class Router(formatOps: FormatOps) { singleArgument && isTreeEndingInArgumentClause(onlyArgument) } ) - if (onlyArgument eq leftOwner) - TokenRanges(TokenRange(ft, afterClose)) + if (onlyArgument eq leftOwner) TokenRanges(TokenRange(ft, close)) else parensTuple(onlyArgument) else insideBracesBlock(ft, close) @@ -1112,14 +1103,16 @@ class Router(formatOps: FormatOps) { else { val penalty = if (!multipleArgs) newlinePenalty else Constants.ShouldBeNewline - policyWithExclude(excludeBlocks, Policy.End.On, Policy.End.On)( - PenalizeAllNewlines( - close, - penalty = penalty, - penalizeLambdas = multipleArgs, - noSyntaxNL = multipleArgs, - ), - ) + policyWithExclude( + excludeBlocks, + Policy.End.OnLeft, + Policy.End.OnLeft, + )(PenalizeAllNewlines( + close, + penalty = penalty, + penalizeLambdas = multipleArgs, + noSyntaxNL = multipleArgs, + )) } val preferNoSplit = !skipNoSplit && singleArgument && @@ -1132,17 +1125,17 @@ class Router(formatOps: FormatOps) { }) val extraOneArgPerLineIndent = if (multipleArgs && style.poorMansTrailingCommasInConfigStyle) - Indent(Num(2), right, After) + Indent(Num(2), afterOpen, After) else Indent.Empty val (implicitPenalty, implicitPolicy) = if (!handleImplicit) (2, Policy.NoPolicy) - else (0, decideNewlinesOnlyAfterToken(right)) + else (0, decideNewlinesOnlyAfterToken(afterOpen)) val splitsNoNL = if (noSplitMod == null) Seq.empty else if (onlyConfigStyle) Seq( Split(noSplitMod, 0).withPolicy(oneArgOneLine & implicitPolicy) - .withOptimalToken(right, killOnFail = true) + .withOptimalToken(afterOpen, killOnFail = true) .withIndents(extraOneArgPerLineIndent, indent), ) else { @@ -1211,7 +1204,7 @@ class Router(formatOps: FormatOps) { !configStyleFlag val policy = if (!noSplitForNL) newlinePolicy - else decideNewlinesOnlyBeforeToken(matching(right).left) + else decideNewlinesOnlyBeforeToken(matching(right)) Split(NoSplit.orNL(noSplitForNL), cost, policy = policy) .andPolicy(Policy ? noConfigStyle && singleLine(4)).andPolicy( Policy ? singleArgument && asInfixApp(args.head) @@ -1226,8 +1219,7 @@ class Router(formatOps: FormatOps) { case FT(open @ LeftParenOrBracket(), right, _) if style.binPack.defnSiteFor(open) != BinPack.Site.Never && isParamClauseSite(leftOwner) => - val closeFt = matching(open) - val close = closeFt.left + val close = matching(open) val noSplitMod = Space(style.spaces.inParentheses) if (close eq right) Seq(Split(noSplitMod, 0)) else { @@ -1247,13 +1239,13 @@ class Router(formatOps: FormatOps) { } val nextCommaOneline = if (binpack.isOneline) nextComma else None - val flags = getBinpackDefnSiteFlags(ft, prev(closeFt)) + val flags = getBinpackDefnSiteFlags(ft, prev(close)) val (nlOnly, nlCloseOnOpen) = flags.nlOpenClose() val noNLPolicy = flags.noNLPolicy val slbOrNL = nlOnly || noNLPolicy == null val rightIsComment = right.is[T.Comment] - def getNoSplit(slbEnd: Option[T])(implicit fileLine: FileLine) = { + def getNoSplit(slbEnd: Option[FT])(implicit fileLine: FileLine) = { val mod = if (rightIsComment) Space.orNL(noBreak()) else noSplitMod slbEnd.fold(Split(mod, 0)) { x => Split(mod, 0).withOptimalToken(x, killOnFail = true) @@ -1266,7 +1258,7 @@ class Router(formatOps: FormatOps) { else if (slbOrNL) getNoSplit(Some(close)) else { val opensPolicy = bracketPenalty.map { p => - Policy.before(close, "PENBP[") { + Policy.beforeLeft(close, "PENBP[") { case Decision(ftd @ FT(o: T.LeftBracket, _, m), s) if isParamClauseSite(m.leftOwner) && styleMap.at(o).binPack.bracketDefnSite @@ -1275,7 +1267,7 @@ class Router(formatOps: FormatOps) { else s.map(x => if (x.isNL) x.withPenalty(p) else x) } } - getNoSplit(nextComma.map(endOfSingleLineBlock)) + getNoSplit(nextComma.map(getSlbEndOnLeft)) .andPolicy((opensPolicy | penalizeBrackets) & noNLPolicy()) } @@ -1290,7 +1282,7 @@ class Router(formatOps: FormatOps) { case NlClosedOnOpen.No => NoPolicy } val nlOnelinePolicy = nextCommaOneline - .map(x => splitOneArgPerLineAfterCommaOnBreak(x.right)) + .map(x => splitOneArgPerLineAfterCommaOnBreak(next(x))) val (indentLen, bpIndentLen) = style.indent .getBinPackDefnSites(leftOwner) @@ -1312,9 +1304,8 @@ class Router(formatOps: FormatOps) { case FT(open @ LeftParenOrBracket(), right, _) if style.binPack.callSiteFor(open) != BinPack.Site.Never && isArgClauseSite(leftOwner) => - val closeFt = matching(open) - val close = closeFt.left - val beforeClose = prev(closeFt) + val close = matching(open) + val beforeClose = prev(close) val isBracket = open.is[T.LeftBracket] val bracketPenalty = if (isBracket) Constants.BracketPenalty else 1 @@ -1346,10 +1337,10 @@ class Router(formatOps: FormatOps) { val newlinesPenalty = 3 + indentLen * bracketPenalty val penalizeNewlinesPolicy = - policyWithExclude(exclude, Policy.End.Before, Policy.End.On) { + policyWithExclude(exclude, Policy.End.BeforeLeft, Policy.End.OnLeft) { val expire = findToken(beforeClose, prev)(x => !RightParenOrBracket(x.left)) - new PenalizeAllNewlines(Policy.End == expire.left, newlinesPenalty) + new PenalizeAllNewlines(Policy.End <= expire, newlinesPenalty) } val (onelineCurryToken, onelinePolicy) = afterFirstArgOneline @@ -1392,16 +1383,15 @@ class Router(formatOps: FormatOps) { if (firstArg.isEmpty) None else if (!oneline) tokens.findTokenEx(ft) { xft => xft.right match { - case `close` | _: T.RightBrace | _: T.RightArrow => null - case x: T.Comma => Right(x) + case close.left | _: T.RightBrace | _: T.RightArrow => null + case _: T.Comma => Right(next(xft)) case x: T.LeftBrace => Left(matching(x)) case _ => Left(next(xft)) } }.toOption else if (isSingleArg) None - else afterFirstArgOneline.map(_.right) - val opt = nextComma - .getOrElse(endOfSingleLineBlock(next(beforeClose))) + else afterFirstArgOneline.map(next) + val opt = nextComma.getOrElse(getSlbEndOnLeft(close)) val slbArg = oneline && !noSplitIndents.exists(_.hasStateColumn) val slbPolicy: Policy = (if (slbArg) nextComma else None).map { @@ -1415,7 +1405,7 @@ class Router(formatOps: FormatOps) { def indentOncePolicy = Policy ? style.binPack.indentCallSiteOnce && { val trigger = getIndentTrigger(leftOwner) - Policy.on(close, prefix = "IND1") { + Policy.onLeft(close, prefix = "IND1") { case Decision(FT(LeftParenOrBracket(), _, m), s) if isArgClauseSite(m.leftOwner) => s.map(x => if (x.isNL) x else x.switch(trigger, false)) @@ -1435,7 +1425,8 @@ class Router(formatOps: FormatOps) { val nlClosedOnOpenEffective = { val nlClosedOnOpenOk = onelineCurryToken.forall(x => - if (x.is[T.Dot]) onelinePolicy.nonEmpty else flags.scalaJsStyle, + if (x.right.is[T.Dot]) onelinePolicy.nonEmpty + else flags.scalaJsStyle, ) val res = if (nlClosedOnOpenOk) nlCloseOnOpen @@ -1476,11 +1467,10 @@ class Router(formatOps: FormatOps) { case FT(left, _: T.Colon, ColonDeclTpeRight(returnType)) if style.newlines.sometimesBeforeColonInMethodReturnType || left.is[T.Comment] && hasBreak() => - val expireFt = getLastNonTrivial(returnType) - val expire = expireFt.left + val expire = getLastNonTrivial(returnType) val sameLineSplit = Space(endsWithSymbolIdent(left)) val bopSplits = style.newlines.getBeforeOpenParenDefnSite.map { x => - val ob = OptionalBraces.at(nextAfterNonComment(expireFt)) + val ob = OptionalBraces.at(nextAfterNonComment(expire)) def extraIfBody = style.indent.extraBeforeOpenParenDefnSite val indent = if (ob) style.indent.getSignificant + extraIfBody @@ -1514,7 +1504,7 @@ class Router(formatOps: FormatOps) { } case FT(_: T.Colon, _, ColonDeclTpeLeft(returnType)) if style.newlines.avoidInResultType => - val expire = getLastNonTrivialToken(returnType) + val expire = getLastNonTrivial(returnType) val policy = PenalizeAllNewlines(expire, Constants.ShouldBeNewline) Seq( Split(Space, 0).withPolicy(policy) @@ -1532,7 +1522,7 @@ class Router(formatOps: FormatOps) { case FT(_: T.Comma, open: T.LeftBrace, _) if !style.poorMansTrailingCommasInConfigStyle && isArgClauseSite(leftOwner) => - val close = matching(open).left + val close = matching(open) val binPackIsEnabled = style.binPack.callSiteFor(leftOwner) != BinPack.Site.Never val useSpace = !style.newlines.keepBreak(newlines) @@ -1544,8 +1534,7 @@ class Router(formatOps: FormatOps) { .Block(List(_: Term.FunctionTerm | _: Term.PartialFunction)) => Seq(Split(Newline, 0)) case _ => - val breakAfter = - endOfSingleLineBlock(next(nextNonCommentSameLine(ft))) + val breakAfter = getSlbEndOnLeft(nextAfterNonCommentSameLine(ft)) val multiLine = decideNewlinesOnlyAfterToken(breakAfter) ==> decideNewlinesOnlyBeforeClose(close) Seq( @@ -1566,7 +1555,7 @@ class Router(formatOps: FormatOps) { ) => Seq(Split(NoSplit, 0)) case FT(_: T.KwMatch, _, _) if !leftOwner.is[Term.Name] => // exclude end marker val indentLen = style.indent.matchSite.fold(0)(_ - style.indent.main) - def expire = getLastNonTrivialToken(leftOwner) // should rbrace + def expire = getLastNonTrivial(leftOwner) // should be rbrace Seq(Split(Space, 0).withIndent(indentLen, expire, ExpiresOn.Before)) case FT(_, lb: T.LeftBrace, _) if ! { // partial initial expr @tailrec @@ -1612,7 +1601,6 @@ class Router(formatOps: FormatOps) { if (binPack eq BinPack.Site.Never) None else optimizationEntities.argument.map { nextArg => val lastFT = getLast(nextArg) - val lastTok = lastFT.left val oneline = binPack.isOneline val afterNextArg = nextNonComment(lastFT) val nextCommaOrParen = afterNextArg.right match { @@ -1640,7 +1628,7 @@ class Router(formatOps: FormatOps) { val sjsExclude = if (binPack ne BinPack.Site.OnelineSjs) TokenRanges.empty - else insideBracesBlock(ft, lastTok) + else insideBracesBlock(ft, lastFT) val onelinePolicy = Policy ? oneline && nextCommaOrParen .map(BinPackOneline.getPolicy(callSite, sjsExclude)(_)._2) @@ -1648,7 +1636,7 @@ class Router(formatOps: FormatOps) { val indentOncePolicy = Policy ? (callSite && style.binPack.indentCallSiteOnce) && { val trigger = getIndentTrigger(leftOwner) - Policy.on(lastTok, prefix = "IND1") { + Policy.onLeft(lastFT, prefix = "IND1") { case Decision(FT(LeftParenOrBracket(), _, m), s) if isArgClauseSite(m.leftOwner) => s.map(x => if (x.isNL) x else x.switch(trigger, true)) @@ -1659,7 +1647,7 @@ class Router(formatOps: FormatOps) { val noSplit = if (style.newlines.keepBreak(newlines)) Split.ignored else { - val end = endOfSingleLineBlock( + val end = getSlbEndOnLeft( nextCommaOrParen.flatMap(getEndOfResultType).getOrElse(lastFT), ) val slbPolicy = @@ -1677,7 +1665,7 @@ class Router(formatOps: FormatOps) { case _: Defn.Val | _: Defn.Var => Seq( Split(Space, 0), Split(Newline, 1) - .withIndent(style.indent.getDefnSite(leftOwner), right, After), + .withIndent(style.indent.getDefnSite(leftOwner), next(ft), After), ) case _: Defn.RepeatedEnumCase if { if (!style.newlines.sourceIgnored) hasBreak() @@ -1686,7 +1674,7 @@ class Router(formatOps: FormatOps) { case _: ImportExportStat => Seq( Split(Space, 0), Split(Newline, 1) - .withIndent(style.indent.main, right, ExpiresOn.After), + .withIndent(style.indent.main, next(ft), ExpiresOn.After), ) case _ => defaultSplits } @@ -1705,7 +1693,7 @@ class Router(formatOps: FormatOps) { ).contains(sc) // something was removed } val policy = Policy ? forceBreak && - decideNewlinesOnlyAfterToken(nextNonCommentSameLineAfter(ft).left) + decideNewlinesOnlyAfterToken(nextNonCommentSameLineAfter(ft)) Seq(Split(NoSplit, 0, policy = policy)) case FT(_: T.KwReturn, r, _) => @@ -1747,7 +1735,7 @@ class Router(formatOps: FormatOps) { case _: T.Supertype => tbounds.lo case _ => None } - val boundEnd = boundOpt.map(getLastNonTrivialToken) + val boundEnd = boundOpt.map(getLastNonTrivial) val typeOwner = rightOwner.parent.get getSplitsForTypeBounds(Space, typeOwner, boundEnd) @@ -1767,7 +1755,7 @@ class Router(formatOps: FormatOps) { case _ => Some(false) }.isDefined => Seq(Split(NoSplit, 0)) - case FT(left, r: T.Dot, GetSelectLike.OnRight(thisSelect)) => + case FT(left, _: T.Dot, GetSelectLike.OnRight(thisSelect)) => val enclosed = style.encloseSelectChains val (expireTree, nextSelect) = findLastApplyAndNextSelect(rightOwner, enclosed) @@ -1776,8 +1764,8 @@ class Router(formatOps: FormatOps) { val afterComment = left.is[T.Comment] val nlOnly = style.newlines.sourceIgnored && afterComment && hasBreak() || - prevApply.exists(x => getHeadToken(x.argClause).is[T.Colon]) - val expire = getLastEnclosedToken(expireTree) + prevApply.exists(x => getHead(x.argClause).left.is[T.Colon]) + val expire = getLastExceptParen(expireTree) val indentLen = style.indent.main def checkFewerBraces(tree: Tree) = tree match { @@ -1794,7 +1782,7 @@ class Router(formatOps: FormatOps) { } def forcedBreakOnNextDotPolicy = nextSelect.map { selectLike => val tree = selectLike.tree - Policy.before(selectLike.nameToken, "NEXTSELFNL") { + Policy.beforeLeft(selectLike.nameFt, "NEXTSELFNL") { case d @ Decision(FT(_, _: T.Dot, m), _) if m.rightOwner eq tree => d.onlyNewlinesWithoutFallback } @@ -1802,7 +1790,7 @@ class Router(formatOps: FormatOps) { def breakOnNextDot: Policy = nextSelect.map { selectLike => val tree = selectLike.tree - Policy.before(selectLike.nameToken, "NEXTSEL2NL") { + Policy.beforeLeft(selectLike.nameFt, "NEXTSEL2NL") { case Decision(FT(_, _: T.Dot, m), s) if m.rightOwner eq tree => val filtered = s.flatMap { x => val y = x.activateFor(SplitTag.SelectChainSecondNL) @@ -1823,7 +1811,7 @@ class Router(formatOps: FormatOps) { def getSlbEnd() = { val nft = nextNonCommentSameLineAfter(ft) val eft = if (nft.noBreak) nextNonCommentSameLineAfter(nft) else nft - endOfSingleLineBlock(eft) + getSlbEndOnLeft(eft) } val ftAfterRight = tokens(ft, 2) @@ -1836,10 +1824,10 @@ class Router(formatOps: FormatOps) { val ko = style.getFewerBraces() == Indents.FewerBraces.always && checkFewerBraces(expireTree) if (ko) None else Some(expire) - }(ns => Some(getLastNonTrivialToken(ns.qual))) + }(ns => Some(getLastNonTrivial(ns.qual))) } { nd => val ok = style.getFewerBraces() == Indents.FewerBraces.never - if (ok) Some(nd.left) else None + if (ok) Some(nd) else None } val altIndent = endSelect.map(Indent(-indentLen, _, After)) NewlineT(alt = Some(ModExt(modSpace).withIndentOpt(altIndent))) @@ -1848,7 +1836,7 @@ class Router(formatOps: FormatOps) { val prevChain = inSelectChain(prevSelect, thisSelect, expireTree) if (canStartSelectChain(thisSelect, nextSelect, expireTree)) { val chainExpire = - if (nextSelect.isEmpty) thisSelect.nameToken else expire + if (nextSelect.isEmpty) thisSelect.nameFt else expire val nestedPenalty = nestedSelect(rightOwner) + nestedApplies(leftOwner) // This policy will apply to both the space and newline splits, otherwise @@ -1890,11 +1878,11 @@ class Router(formatOps: FormatOps) { else { val noSplit = Split(modSpace, 0) if (prevChain) noSplit - else chainExpire match { // allow newlines in final {} block + else chainExpire.left match { // allow newlines in final {} block case x: T.RightBrace => noSplit - .withSingleLine(matching(x).left, noSyntaxNL = true) - case x => noSplit - .withSingleLineNoOptimal(x, noSyntaxNL = true) + .withSingleLine(matching(x), noSyntaxNL = true) + case _ => noSplit + .withSingleLineNoOptimal(chainExpire, noSyntaxNL = true) } }.andPolicy(penalizeBreaks) val nlSplit = Split(if (ignoreNoSplit) Newline else nlMod, nlCost) @@ -1915,12 +1903,13 @@ class Router(formatOps: FormatOps) { case Newlines.keep => if (hasBreak()) Seq(Split(Newline, 0)) - else if (hasBreakAfterRightBeforeNonComment(ft)) + else if (hasBreakAfterRightBeforeNonComment(ft)) { + val nft = next(ft) Seq(Split(modSpace, 0).withPolicy( - decideNewlinesOnlyAfterClose(Split(Newline, 0))(r), - ignore = next(ft).right.is[T.Comment], + decideNewlinesOnlyAfterClose(nft), + ignore = nft.right.is[T.Comment], )) - else Seq( + } else Seq( Split(modSpace, 0), Split(Newline, 1).onlyFor(SplitTag.SelectChainBinPackNL), ) @@ -1946,7 +1935,7 @@ class Router(formatOps: FormatOps) { if (nlOnly) Seq(nlSplitBase(0)) else { val end = nextSelect.fold(expire) { x => - prevNonCommentBefore(tokenBefore(x.nameFt)).left + prevNonCommentBefore(tokenBefore(x.nameFt)) } val exclude = insideBracesBlock(ft, end, parensToo = true) .excludeCloseDelim @@ -1977,7 +1966,7 @@ class Router(formatOps: FormatOps) { val delayedBreakPolicyOpt = nextSelect.map { selectLike => val tree = selectLike.tree - Policy.before(selectLike.nameToken, "NEXTSEL1NL") { + Policy.beforeLeft(selectLike.nameFt, "NEXTSEL1NL") { case Decision(FT(_, _: T.Dot, m), s) if m.rightOwner eq tree => SplitTag.SelectChainFirstNL.activateOnly(s) case Decision(FT(l, _: T.Comment, m), s) @@ -2003,7 +1992,7 @@ class Router(formatOps: FormatOps) { nextSelect.isEmpty && checkFewerBraces(expireTree) if (ok) nlIndent else Indent.empty } { x => - if (fbIndent) Indent(indentLen, x.left, Before) else Indent.Empty + if (fbIndent) Indent(indentLen, x, Before) else Indent.Empty } baseSplits.map { s => if (s.isNL) s.withIndent(nlIndent).andFirstPolicy(nlPolicy) @@ -2089,7 +2078,7 @@ class Router(formatOps: FormatOps) { ) case enumCase: Defn.EnumCase => val indent = style.indent.withSiteRelativeToExtends - val expire = getLastToken(enumCase) + val expire = getLast(enumCase) Seq( Split(Space, 0).withIndent(indent, expire, ExpiresOn.After), Split(Newline, 1).withIndent(indent, expire, ExpiresOn.After), @@ -2103,12 +2092,12 @@ class Router(formatOps: FormatOps) { !isTokenHeadOrBefore(open, leftOwner) case _ => false }) => - val close = matching(open).left + val close = matching(open) val indentLen = style.indent.ctrlSite.getOrElse(style.indent.callSite) def indents = if (style.align.openParenCtrlSite) getOpenParenAlignIndents(close) else Seq(Indent(indentLen, close, ExpiresOn.Before)) - val penalizeNewlines = penalizeNewlineByNesting(open, close) + val penalizeNewlines = penalizeNewlineByNesting(ft, close) def baseNoSplit(commentNL: Modification = null)(implicit fileLine: FileLine, ) = Split.opt(getNoSplitAfterOpening(ft, commentNL = commentNL), 0) @@ -2131,7 +2120,7 @@ class Router(formatOps: FormatOps) { case FT(_: T.KwIf, right, _) if leftOwner.is[Term.If] => val owner = leftOwner.asInstanceOf[Term.If] - val expire = getLastToken(owner) + val expire = getLast(owner) val isKeep = style.newlines.keep val mod = if (isKeep && hasBreak()) Newline @@ -2185,14 +2174,14 @@ class Router(formatOps: FormatOps) { case _ => t } } - val expire = getLastToken(body) + val expire = getLast(body) def nlSplitFunc(cost: Int)(implicit l: sourcecode.Line) = Split(Newline2x(ft), cost).withIndent(style.indent.main, expire, After) if (style.newlines.getBeforeMultiline eq Newlines.unfold) CtrlBodySplits .checkComment(nlSplitFunc) { nft => if (nft.right.is[T.LeftBrace]) { val nextFt = nextNonCommentSameLineAfter(nft) - val policy = decideNewlinesOnlyAfterToken(nextFt.left) + val policy = decideNewlinesOnlyAfterToken(nextFt) Seq(Split(Space, 0, policy = policy)) } else Seq(nlSplitFunc(0)) } @@ -2208,7 +2197,7 @@ class Router(formatOps: FormatOps) { if (left.is[T.RightBrace]) Seq(Split(Space, 0)) else Seq( if (style.newlines.okSpaceForSource(newlines)) { - val expire = getLastToken(rightOwner) + val expire = getLast(rightOwner) Split(Space, 0).withSingleLineNoOptimal( expire, exclude = insideBracesBlock(ft, expire), @@ -2221,7 +2210,7 @@ class Router(formatOps: FormatOps) { val okSpace = style.newlines.sourceIgnored || noBreak() Seq( Split(!okSpace, 0)(Space).withOptimalToken( - nextNonCommentSameLineAfter(ft).left, + nextNonCommentSameLineAfter(ft), killOnFail = false, ), Split(Newline, 1), @@ -2238,7 +2227,7 @@ class Router(formatOps: FormatOps) { case x => throw new UnexpectedTree[Term.If](x) }) => val body = leftOwner.asInstanceOf[Term.If].elsep - val expire = getLastToken(body) + val expire = getLast(body) def nlSplitFunc(cost: Int) = Split(Newline2x(ft), cost) .withIndent(style.indent.main, expire, After) if (style.newlines.getBeforeMultiline eq Newlines.unfold) @@ -2261,13 +2250,12 @@ class Router(formatOps: FormatOps) { Seq(Split(NoSplit, 0)) case FT(open: T.LeftParen, right, _) => - val closeFt = matching(open) - val close = closeFt.left - val beforeClose = prev(closeFt) + val close = matching(open) + val beforeClose = prev(close) implicit val clauseSiteFlags = ClauseSiteFlags.atCallSite(leftOwner) val isConfig = couldPreserveConfigStyle(ft, beforeClose.hasBreak) - val enclosed = findEnclosedBetweenParens(open, close, leftOwner) + val enclosed = findEnclosedBetweenParens(open, close.left, leftOwner) def spaceSplitWithoutPolicy(implicit fileLine: FileLine) = { val indent: Length = right match { case _: T.KwIf => StateColumn @@ -2329,51 +2317,47 @@ class Router(formatOps: FormatOps) { // Case case FT(_: T.KwCase, _, _) if leftOwner.is[Defn.RepeatedEnumCase] => val indent = - Indent(style.indent.main, leftOwner.tokens.last, ExpiresOn.After) + Indent(style.indent.main, getLast(leftOwner), ExpiresOn.After) Seq(Split(Space, 0).withIndent(indent)) case FT(_: T.KwCase, _, _) if leftOwner.is[CaseTree] => val owner = leftOwner.asInstanceOf[CaseTree] - val arrowFt = leftOwner match { + val arrow = leftOwner match { case c: Case => getCaseArrow(c) case tc: TypeCase => getCaseArrow(tc) } - val arrow = arrowFt.left - val postArrowFt = nextNonCommentSameLine(arrowFt) - val postArrow = postArrowFt.left + val postArrow = nextNonCommentSameLine(arrow) val ownerEnd = getLast(owner) - val expire = nextNonCommentSameLine(ownerEnd).left + val expire = nextNonCommentSameLine(ownerEnd) - val bodyBlock = isCaseBodyABlock(arrowFt, owner) + val bodyBlock = isCaseBodyABlock(arrow, owner) def defaultPolicy = decideNewlinesOnlyAfterToken(postArrow) val postArrowPolicy = - if (bodyBlock || (arrowFt ne postArrowFt) && postArrowFt.hasBreak) - NoPolicy + if (bodyBlock || (arrow ne postArrow) && postArrow.hasBreak) NoPolicy else { // postArrowFt points to non-comment after arrowFt // possibly on next line without intervening comments implicit val beforeMultiline = style.newlines.getBeforeMultiline - getClosingIfCaseBodyEnclosedAsBlock(postArrowFt, owner).fold { + getClosingIfCaseBodyEnclosedAsBlock(postArrow, owner).fold { Policy ? beforeMultiline.in(Newlines.fold, Newlines.keep) || defaultPolicy - } { rparenFt => - val lparentFt = next(postArrowFt) - val postParenFt = nextNonCommentSameLine(lparentFt) - val rparen = rparenFt.right + } { rparen => + val lparen = next(postArrow) + val postParen = nextNonCommentSameLine(lparen) val indent = style.indent.main val lindents = Seq( - Indent(indent, rparen, Before), + Indent(indent, next(rparen), Before), Indent(-indent, expire, After), ) - val lmod = NewlineT(noIndent = rhsIsCommentedOut(postParenFt)) + val lmod = NewlineT(noIndent = rhsIsCommentedOut(postParen)) val lsplit = Seq(Split(lmod, 0).withIndents(lindents)) val rsplit = Seq(Split(Newline, 0)) - Policy.after(postParenFt.left, prefix = "CASE[(]") { - case Decision(`postParenFt`, _) => lsplit + Policy.onRight(postParen, prefix = "CASE[(]") { + case Decision(`postParen`, _) => lsplit // fires only if there's a comment between lparentFt and postParentFt - case Decision(`lparentFt`, _) => Seq(Split(Space, 0)) - } ==> Policy.on(rparen, prefix = "CASE[)]") { - case Decision(`rparenFt`, _) => rsplit + case Decision(`lparen`, _) => Seq(Split(Space, 0)) + } ==> Policy.afterRight(rparen, prefix = "CASE[)]") { + case Decision(`rparen`, _) => rsplit } } } @@ -2386,30 +2370,28 @@ class Router(formatOps: FormatOps) { ) val mod = ModExt(Space, indents) val slbExpireOpt = prevNotTrailingComment(ownerEnd).toOption - val policy = slbExpireOpt.fold(postArrowPolicy) { slbExpireFt => - val slbExpire = slbExpireFt.left - val onArrowPolicy = Policy.End == arrow ==> - Policy.after(postArrow, "CASESLB>ARROW") { case Decision(_, ss) => + val policy = slbExpireOpt.fold(postArrowPolicy) { slbExpire => + val onArrowPolicy = Policy.End <= arrow ==> + Policy.onRight(postArrow, "CASESLB>ARROW") { case Decision(_, ss) => ss.flatMap { s => - Seq( - s.notIf(s.isNL).withSingleLine(slbExpire, extend = true), - s.andPolicy(postArrowPolicy), - ) + val split = s.andPolicy(postArrowPolicy) + if (s.isNL) Seq(split) + else Seq(s.withSingleLine(slbExpire, extend = true), split) } } Policy.RelayOnSplit((s, _) => s.isNL)(onArrowPolicy)(postArrowPolicy) } Seq(Split(mod, 0, policy = policy)) - case FT(_, cond: T.KwIf, _) if rightOwner.is[Case] => - val arrow = getCaseArrow(rightOwner.asInstanceOf[Case]).left + case FT(_, _: T.KwIf, _) if rightOwner.is[Case] => + val arrow = getCaseArrow(rightOwner.asInstanceOf[Case]) val noSplit = if (style.newlines.keepBreak(newlines)) Split.ignored else { val afterIf = nextNonCommentSameLineAfter(ft) if (style.newlines.keepBreak(afterIf)) { val indent = Indent(style.indent.main, arrow, ExpiresOn.Before) - Split(Space, 0).withSingleLine(afterIf.left).withIndent(indent) + Split(Space, 0).withSingleLine(afterIf).withIndent(indent) } else { val exclude = insideBracesBlock(ft, arrow) Split(Space, 0).withSingleLine( @@ -2421,7 +2403,7 @@ class Router(formatOps: FormatOps) { } Seq( noSplit, - Split(Newline, 1).withPolicy(penalizeNewlineByNesting(cond, arrow)), + Split(Newline, 1).withPolicy(penalizeNewlineByNesting(next(ft), arrow)), ) case FT(_: T.KwIf, _, _) if leftOwner.is[Case] => @@ -2446,7 +2428,7 @@ class Router(formatOps: FormatOps) { } case t => t } - val end = getLastToken(body) + val end = getLast(body) val indent = Indent(style.indent.main, end, ExpiresOn.After) CtrlBodySplits.get(body, Seq(indent)) { Split(Space, 0).withSingleLineNoOptimal(end) @@ -2454,7 +2436,7 @@ class Router(formatOps: FormatOps) { // Term.ForYield case FT(T.KwYield(), _, _) if leftOwner.is[Term.ForYield] => - val lastToken = getLastToken(leftOwner.asInstanceOf[Term.ForYield].body) + val lastToken = getLast(leftOwner.asInstanceOf[Term.ForYield].body) val indent = Indent(style.indent.main, lastToken, ExpiresOn.After) if (style.newlines.avoidAfterYield && !rightOwner.is[Term.If]) { val noIndent = !isRightCommentWithBreak(ft) @@ -2472,7 +2454,7 @@ class Router(formatOps: FormatOps) { !nextNonComment(ft).right.is[T.KwDo] => val body = leftOwner.parent.get.asInstanceOf[Term.For].body def nlSplit(cost: Int) = Split(Newline2x(ft), cost) - .withIndent(style.indent.main, getLastToken(body), After) + .withIndent(style.indent.main, getLast(body), After) CtrlBodySplits.get(body)(null)(nlSplit) // After comment @@ -2484,7 +2466,7 @@ class Router(formatOps: FormatOps) { val nft = nextNonCommentAfter(ft) def baseSplit(implicit fileLine: FileLine) = Split(mod, 0) def split(implicit fileLine: FileLine) = baseSplit - .withIndent(style.indent.main, nft.left, ExpiresOn.After) + .withIndent(style.indent.main, nft, ExpiresOn.After) nft match { case FT(_, _: T.Dot, GetSelectLike.OnRight(t)) => @@ -2504,7 +2486,7 @@ class Router(formatOps: FormatOps) { !style.verticalMultiline.atDefnSite => val spaceSplit = Split(Space, 0) .notIf(style.newlines.forceAfterImplicitParamListModifier).withPolicy( - SingleLineBlock(params.tokens.last), + SingleLineBlock(getLast(params)), style.newlines.notPreferAfterImplicitParamListModifier, ) Seq(spaceSplit, Split(Newline, if (spaceSplit.isActive) 1 else 0)) @@ -2543,9 +2525,10 @@ class Router(formatOps: FormatOps) { case t: Pat.Alternative => t.lhs case t => t }) - val opt = nextNonComment(end).right match { - case _: T.KwIf => end.left - case x => x + val nft = nextAfterNonComment(end) + val opt = nft.left match { + case _: T.KwIf => end + case _ => nft } Seq( Split(Space, 0).withOptimalToken(opt, killOnFail = false), @@ -2583,7 +2566,7 @@ class Router(formatOps: FormatOps) { * "if startsStatement" matches */ style.newlines.source match { case Newlines.fold => - val endOfGuard = getLastToken(rightOwner) + val endOfGuard = getLast(rightOwner) val exclude = insideBracesBlock(ft, endOfGuard, true) Seq( Split(Space, 0).withSingleLine(endOfGuard, exclude = exclude), @@ -2636,16 +2619,15 @@ class Router(formatOps: FormatOps) { case FT(_: T.KwDo, _, _) if leftOwner.is[Term.Do] => val owner = leftOwner.asInstanceOf[Term.Do] val eft = getLast(owner.body) - val end = eft.left - val indent = Indent(style.indent.main, end, ExpiresOn.After) - val kwWhile = nextNonComment(eft).right + val indent = Indent(style.indent.main, eft, ExpiresOn.After) + val kwWhile = nextAfterNonComment(eft) val noSplit = if (noBreak() && isRightCommentThenBreak(ft)) Split(Space, 0) .withIndents(indent) else { - val exclude = insideBracesBlock(ft, end, parensToo = true) + val exclude = insideBracesBlock(ft, eft, parensToo = true) Split(Space, 0) - .withSingleLine(endOfSingleLineBlock(eft), exclude = exclude) + .withSingleLine(getSlbEndOnLeft(eft), exclude = exclude) } val nlSplit = Split(Newline, 1, policy = decideNewlinesOnlyBeforeToken(kwWhile)) @@ -2719,12 +2701,12 @@ class Router(formatOps: FormatOps) { if (ft.noBreak) splits.map(_.withMod(Space)) else splitsAsNewlines(splits) case ft if ft.meta.formatOff && ft.hasBreak => splitsAsNewlines(splits) - case FT(_: T.Equals, r: T.KwMacro, _) + case FT(_: T.Equals, _: T.KwMacro, _) if dialect.allowSignificantIndentation => // scala3 compiler doesn't allow newline before `macro` splits.map { s => if (!s.isNL) s - else s.withMod(Space).andPolicy(decideNewlinesOnlyAfterClose(r)) + else s.withMod(Space).andPolicy(decideNewlinesOnlyAfterClose(next(ft))) } case _ => splits } @@ -2742,7 +2724,7 @@ class Router(formatOps: FormatOps) { else if (isRightCommentWithBreak(ft)) Seq(CtrlBodySplits.withIndent(Split(Space.orNL(ft), 0), body, endFt)) else if (isJsNative(body)) - Seq(Split(Space, 0).withSingleLineNoOptimal(endFt.left)) + Seq(Split(Space, 0).withSingleLineNoOptimal(endFt)) else if ( style.dialect.allowSignificantIndentation && (style.newlines.sourceIgnored || ft.noBreak) && body.parent.exists { @@ -2765,7 +2747,6 @@ class Router(formatOps: FormatOps) { style: ScalafmtConfig, ft: FT, ): Seq[Split] = { - val expire = endFt.left def baseSplit = Split(Space, 0) def newlineSplit(cost: Int)(implicit fileLine: FileLine) = CtrlBodySplits .withIndent(Split(Newline2x(ft), cost), body, endFt) @@ -2781,7 +2762,7 @@ class Router(formatOps: FormatOps) { case Newlines.keep if ft.hasBreak => Seq(newlineSplit(0)) case Newlines.unfold => - Seq(baseSplit.withSingleLine(expire), newlineSplit(1)) + Seq(baseSplit.withSingleLine(endFt), newlineSplit(1)) case x => CtrlBodySplits.folded(body, x eq Newlines.keep)(newlineSplit) } @@ -2800,16 +2781,14 @@ class Router(formatOps: FormatOps) { def wouldDangle = ft.meta.leftOwner.parent .exists(style.danglingParentheses.atSite(_, false)) - val expire = endFt.left // rhsOptimalToken is too aggressive here - val optimalFt = endFt.right match { + val optimal = endFt.right match { case _: T.Comma => next(endFt) case RightParenOrBracket() if !wouldDangle => next(endFt) case _ => endFt } - val optimal = optimalFt.left - def optimalWithComment = optimalFt.right match { - case x: T.Comment if optimalFt.noBreak => x + def optimalWithComment = optimal.right match { + case _: T.Comment if optimal.noBreak => next(optimal) case _ => optimal } @@ -2825,9 +2804,9 @@ class Router(formatOps: FormatOps) { Split(isRightCommentThenBreak(ft), 0)(Space) def twoBranches(implicit fileLine: FileLine) = baseSpaceSplit .withOptimalToken(optimal, killOnFail = false).withPolicy { - val exclude = insideBracesBlock(ft, expire) - policyWithExclude(exclude, Policy.End.On, Policy.End.After)( - PenalizeAllNewlines(expire, Constants.ShouldBeSingleLine), + val exclude = insideBracesBlock(ft, endFt) + policyWithExclude(exclude, Policy.End.OnLeft, Policy.End.OnRight)( + PenalizeAllNewlines(endFt, Constants.ShouldBeSingleLine), ) } val body = CtrlBodySplits.getBlockStat(rawBody) @@ -2851,10 +2830,9 @@ class Router(formatOps: FormatOps) { ft: FT, ): Seq[Split] = maybeGetInfixSplitsBeforeLhs() { val endFt = getLastNonTrivial(body) - val expire = endFt.left val spaceIndents = if (!style.align.arrowEnumeratorGenerator) Seq.empty - else Seq(Indent(StateColumn, expire, After)) + else Seq(Indent(StateColumn, endFt, After)) getSplitsDefValEquals(body, endFt, spaceIndents) { CtrlBodySplits.get(body, spaceIndents) { if (spaceIndents.nonEmpty) Split(Space, 0).withIndents(spaceIndents) @@ -2865,8 +2843,8 @@ class Router(formatOps: FormatOps) { case _ => true } if (noSlb) Split(Space, 0) - .withOptimalToken(ft.right, killOnFail = false) - else Split(Space, 0).withSingleLine(expire) + .withOptimalToken(next(ft), killOnFail = false) + else Split(Space, 0).withSingleLine(endFt) } }(cost => CtrlBodySplits.withIndent(Split(Newline2x(ft), cost), endFt)) } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala index 271eef914..507c51bf3 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala @@ -8,14 +8,14 @@ import org.scalameta.FileLine import scala.meta.tokens.{Token => T} case class OptimalToken( - token: T, + token: FT, killOnFail: Boolean, recurseOnly: Boolean = false, ) { override def toString: String = { - val kof = if (killOnFail) "!" else ":" + val kof = if (killOnFail) "!" else "" val rec = if (recurseOnly) "+" else "" - s"$token$kof${token.end}$rec" + s"${token.left}$kof[${token.idx}]$rec" } } @@ -144,7 +144,7 @@ case class Split( else copy()(fileLineStack.forThisLine(fileLine.file, fileLine.line)) def withOptimalTokenOpt( - token: => Option[T], + token: => Option[FT], killOnFail: Boolean, extend: Boolean = false, recurseOnly: Boolean = false, @@ -155,7 +155,7 @@ case class Split( ) def withOptimalToken( - token: => T, + token: => FT, killOnFail: Boolean, ignore: Boolean = false, extend: Boolean = false, @@ -180,7 +180,7 @@ case class Split( this.optimalAt match { case None => amend() case Some(x) if extend => - if (x.token.end < opt.token.end) amend() else this + if (x.token.idx < opt.token.idx) amend() else this case _ => throw new UnsupportedOperationException("Can't reset optimal token") } @@ -194,11 +194,13 @@ case class Split( if (ignore) this else if (extend) andPolicy(newPolicy) else if (isIgnored) this - else if (policy.isEmpty) copy(policy = newPolicy) - else throw new UnsupportedOperationException("Use orPolicy or andPolicy") + else if (policy.isEmpty) { + val policyEval = newPolicy + if (policyEval.isEmpty) this else copy(policy = policyEval) + } else throw new UnsupportedOperationException("Use orPolicy or andPolicy") def withSingleLine( - expire: => T, + expire: => FT, exclude: => TokenRanges = TokenRanges.empty, noSyntaxNL: Boolean = false, killOnFail: => Option[Boolean] = None, @@ -228,7 +230,7 @@ case class Split( } def withSingleLineNoOptimal( - expire: => T, + expire: => FT, exclude: => TokenRanges = TokenRanges.empty, noSyntaxNL: Boolean = false, rank: Int = 0, @@ -259,19 +261,19 @@ case class Split( if (isIgnored || penalty <= 0) this else copy(penalty = this.penalty + penalty) - def withIndent(length: => Length, expire: => T, when: ExpiresOn): Split = + def withIndent(length: => Length, expire: => FT, when: ExpiresOn): Split = withIndent(length, expire, when, ignore = false) def withIndent( length: => Length, - expire: => T, + expire: => FT, when: ExpiresOn, ignore: Boolean, ): Split = withMod(modExt.withIndent(length, expire, when), ignore) def withIndentOpt( length: => Length, - expire: Option[T], + expire: Option[FT], when: ExpiresOn, ): Split = withMod(modExt.withIndentOpt(length, expire, when)) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala index d162efece..90f62fbe5 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala @@ -75,7 +75,7 @@ final class State( (modExt.getActualIndents(offset) ++ indents).flatMap { x => if (x.notExpiredBy(tok)) Some(x) else extendedEnd - .map(y => x.copy(expireEnd = y, expiresAt = ExpiresOn.After)) + .map(y => x.copy(expire = y, expiresAt = ExpiresOn.After)) } } @@ -288,11 +288,11 @@ final class State( private def getRelativeToLhsLastLineEnd( isNL: Boolean, - )(implicit style: ScalafmtConfig, tokens: FormatTokens): Option[Int] = { + )(implicit style: ScalafmtConfig, tokens: FormatTokens): Option[FT] = { val allowed = style.indent.relativeToLhsLastLine - def treeEnd(x: Tree) = tokens.getLast(x).left.end - def indentEnd(ft: FT, isNL: Boolean)(onComment: => Option[Int]) = { + def treeEnd(x: Tree) = tokens.getLast(x) + def indentEnd(ft: FT, isNL: Boolean)(onComment: => Option[FT]) = { val leftOwner = ft.meta.leftOwner ft.left match { case _: T.KwMatch @@ -315,7 +315,7 @@ final class State( val tok = tokens(depth) val right = tok.right if (allowed.isEmpty) None - else if (!isNL && right.is[T.Comment]) Some(right.end) + else if (!isNL && right.is[T.Comment]) Some(tokens.next(tok)) else indentEnd(tok, isNL) { val earlierState = prev.prevNonCommentSameLine indentEnd(tokens(earlierState.depth), earlierState.split.isNL)(None) @@ -328,7 +328,7 @@ final class State( allowed.contains(Indents.RelativeToLhs.`infix`) case _ => false }) - if (delay) Some(right.end) else None + if (delay) Some(tokens.next(tok)) else None } } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala index a616ff3bc..f82327451 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala @@ -31,7 +31,7 @@ object PolicyOps { object PenalizeAllNewlines { def apply( - expire: T, + expire: FT, penalty: Int, penalizeLambdas: Boolean = true, noSyntaxNL: Boolean = false, @@ -45,15 +45,15 @@ object PolicyOps { ) } - def penalizeNewlineByNesting(from: T, to: T)(implicit + def penalizeNewlineByNesting(before: FT, after: FT)(implicit fileLine: FileLine, - ): Policy = Policy.End < from ==> - Policy.before(to, prefix = "PNL()") { case Decision(FT(l, _, m), s) => + ): Policy = Policy.End < before ==> Policy.beforeLeft(after, prefix = "PNL()") { + case Decision(FT(l, _, m), s) => val nonBoolPenalty = if (TokenOps.isBoolOperator(l)) 0 else 5 val penalty = TreeOps.nestedSelect(m.leftOwner) + TreeOps.nestedApplies(m.rightOwner) + nonBoolPenalty s.map(x => if (x.isNL) x.withPenalty(penalty) else x) - } + } /** Forces all splits up to including expire to be on a single line. * @param okSLC @@ -82,15 +82,15 @@ object PolicyOps { object SingleLineBlock { def apply( - expire: T, + expire: FT, exclude: TokenRanges = TokenRanges.empty, okSLC: Boolean = false, noSyntaxNL: Boolean = false, rank: Int = 0, )(implicit fileLine: FileLine, style: ScalafmtConfig): Policy = - policyWithExclude(exclude, Policy.End.On, Policy.End.After)( + policyWithExclude(exclude, Policy.End.OnLeft, Policy.End.OnRight)( new SingleLineBlock( - Policy.End == expire, + Policy.End <= expire, okSLC = okSLC, noSyntaxNL = noSyntaxNL, rank = rank, @@ -99,52 +99,51 @@ object PolicyOps { } final class DecideNewlinesOnlyBeforeToken( - val token: T, + val token: FT, split: Option[Split], val rank: Int = 0, ifAny: Boolean = false, )(implicit fileLine: FileLine) extends Policy.Clause { - override val endPolicy: Policy.End.WithPos = Policy.End == token + override val endPolicy: Policy.End.WithPos = Policy.End <= token override val noDequeue: Boolean = false override val prefix: String = "NB" override val f: Policy.Pf = split match { case Some(s) => { - case d: Decision if d.formatToken.right eq token => + case d: Decision if d.formatToken.right eq token.left => d.onlyNewlinesWithFallback(s) } case _ if ifAny => { - case d: Decision if d.formatToken.right eq token => + case d: Decision if d.formatToken.right eq token.left => d.onlyNewlinesIfAvailable } case _ => { - case d: Decision if d.formatToken.right eq token => + case d: Decision if d.formatToken.right eq token.left => d.onlyNewlinesWithoutFallback } } } final class DecideNewlinesOnlyAfterToken( - val token: T, + val token: FT, split: Option[Split], val rank: Int = 0, ifAny: Boolean = false, )(implicit fileLine: FileLine) extends Policy.Clause { - override val endPolicy: Policy.End.WithPos = Policy.End > token + override val endPolicy: Policy.End.WithPos = Policy.End >= token override val noDequeue: Boolean = false override val prefix: String = "NA" override val f: Policy.Pf = split match { case Some(s) => { - case d: Decision if d.formatToken.left eq token => + case d: Decision if d.formatToken eq token => d.onlyNewlinesWithFallback(s) } case _ if ifAny => { - case d: Decision if d.formatToken.left eq token => - d.onlyNewlinesIfAvailable + case d: Decision if d.formatToken eq token => d.onlyNewlinesIfAvailable } case _ => { - case d: Decision if d.formatToken.left eq token => + case d: Decision if d.formatToken eq token => d.onlyNewlinesWithoutFallback } } @@ -152,11 +151,11 @@ object PolicyOps { def policyWithExclude( exclude: TokenRanges, - endLt: T => Policy.End.WithPos, - endRt: T => Policy.End.WithPos, + endLt: FT => Policy.End.WithPos, + endRt: FT => Policy.End.WithPos, )(lastPolicy: Policy)(implicit fileLine: FileLine): Policy = exclude.ranges .foldLeft(lastPolicy) { case (policy, range) => - (lastPolicy <== endLt(range.lt.left)) ==> (endRt(range.rt.left) ==> policy) + (lastPolicy <== endLt(range.lt)) ==> (endRt(range.rt) ==> policy) } def delayedBreakPolicy( @@ -164,82 +163,80 @@ object PolicyOps { exclude: TokenRanges = TokenRanges.empty, )(onBreakPolicy: Policy)(implicit fileLine: FileLine): Policy = Policy ? onBreakPolicy.isEmpty || - policyWithExclude(exclude, Policy.End.On, Policy.End.After)( + policyWithExclude(exclude, Policy.End.OnLeft, Policy.End.OnRight)( new Policy.Map(end, desc = onBreakPolicy.toString)({ s => if (s.isNL) s.orPolicy(onBreakPolicy) else s }), ) - def delayedBreakPolicyBefore(token: T)(onBreakPolicy: Policy): Policy = + def delayedBreakPolicyBefore(token: FT)(onBreakPolicy: Policy): Policy = delayedBreakPolicy(Policy.End < token)(onBreakPolicy) - def delayedBreakPolicyFor(token: T)(f: T => Policy): Policy = + def delayedBreakPolicyFor(token: FT)(f: FT => Policy): Policy = delayedBreakPolicyBefore(token)(f(token)) - def decideNewlinesOnlyBeforeClose(close: T)(implicit + def decideNewlinesOnlyBeforeClose(close: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyBeforeClose(0)(close) - def decideNewlinesOnlyBeforeClose(rank: Int)(close: T)(implicit + def decideNewlinesOnlyBeforeClose(rank: Int)(close: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyBeforeClose(Split(Newline, 0), rank)(close) - def decideNewlinesOnlyBeforeClose(split: Split, rank: Int = 0)(close: T)( + def decideNewlinesOnlyBeforeClose(split: Split, rank: Int = 0)(close: FT)( implicit fileLine: FileLine, ): Policy = new DecideNewlinesOnlyBeforeToken(close, Option(split), rank) - def decideNewlinesOnlyBeforeCloseOnBreak(close: T)(implicit + def decideNewlinesOnlyBeforeCloseOnBreak(close: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyBeforeCloseOnBreak(0)(close) - def decideNewlinesOnlyBeforeCloseOnBreak(rank: Int)(close: T)(implicit + def decideNewlinesOnlyBeforeCloseOnBreak(rank: Int)(close: FT)(implicit fileLine: FileLine, ): Policy = delayedBreakPolicyFor(close)(decideNewlinesOnlyBeforeClose(rank)) - def decideNewlinesOnlyBeforeToken(token: T)(implicit + def decideNewlinesOnlyBeforeToken(token: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyBeforeToken(0)(token) def decideNewlinesOnlyBeforeToken(rank: Int, ifAny: Boolean = false)( - token: T, + token: FT, )(implicit fileLine: FileLine): Policy = new DecideNewlinesOnlyBeforeToken(token, None, rank = rank, ifAny = ifAny) - def decideNewlinesOnlyAfterClose(close: T)(implicit + def decideNewlinesOnlyAfterClose(close: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyAfterClose(0)(close) - def decideNewlinesOnlyAfterClose(rank: Int)(close: T)(implicit + def decideNewlinesOnlyAfterClose(rank: Int)(close: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyAfterClose(Split(Newline, 0), rank)(close) - def decideNewlinesOnlyAfterClose(split: Split, rank: Int = 0)(close: T)( + def decideNewlinesOnlyAfterClose(split: Split, rank: Int = 0)(close: FT)( implicit fileLine: FileLine, ): Policy = new DecideNewlinesOnlyAfterToken(close, Option(split), rank) - def decideNewlinesOnlyAfterToken(token: T)(implicit + def decideNewlinesOnlyAfterToken(token: FT)(implicit fileLine: FileLine, ): Policy = decideNewlinesOnlyAfterToken(0)(token) def decideNewlinesOnlyAfterToken(rank: Int, ifAny: Boolean = false)( - token: T, + token: FT, )(implicit fileLine: FileLine): Policy = new DecideNewlinesOnlyAfterToken(token, None, rank = rank, ifAny = ifAny) def unindentAtExclude(exclude: TokenRanges, indent: Length): Policy = exclude .ranges.foldLeft(Policy.noPolicy) { case (policy, range) => val (lt, rt) = (range.lt, range.rt) - val ltl = lt.left - val rtl = rt.left - val trigger = rtl - val unindent = Indent(indent, rtl, ExpiresOn.After) + val trigger = rt.left + val unindent = Indent(indent, rt, ExpiresOn.After) val triggeredIndent = Indent.before(unindent, trigger) val triggerUnindent = Policy - .on(rtl, prefix = "UNIND{") { case Decision(`lt`, s) => + .onLeft(rt, prefix = "UNIND{") { case Decision(`lt`, s) => s.map(_.withIndent(triggeredIndent)) } - val cancelUnindent = delayedBreakPolicy(Policy.End == ltl) { - Policy.after(ltl, rank = 1, prefix = "UNIND}") { // use rank to apply after policy above + val cancelUnindent = delayedBreakPolicy(Policy.End <= lt) { + Policy.onRight(lt, rank = 1, prefix = "UNIND}") { // use rank to apply after policy above case Decision(`lt`, s) => s.map(_.switch(trigger, false)) } } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala index b2dbfbb2d..9abae648c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala @@ -575,7 +575,7 @@ object TreeOps { case _ => Some(false) }.isDefined - def getEndOfFirstCall(tree: Tree)(implicit ftoks: FormatTokens): Option[T] = { + def getEndOfFirstCall(tree: Tree)(implicit ftoks: FormatTokens) = { @tailrec def traverse(tree: Tree, res: Option[Tree]): Option[Tree] = tree match { case t: Term.Select if res.isDefined => traverse(t.qual, Some(t.qual)) @@ -586,7 +586,7 @@ object TreeOps { traverse(arg, res) case _ => res } - traverse(tree, None).map(_.tokens.last) + traverse(tree, None).map(ftoks.getLast) } @inline