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 572b918df3..1f96b9a304 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 @@ -162,7 +162,7 @@ private class BestFirstSearch private ( implicit val style = styleMap.at(splitToken) - if (curr.split != null && curr.split.modification.isNewline) { + if (curr.split != null && curr.split.isNL) { val tokenHash = hash(splitToken.left) if ( emptyQueueSpots.contains(tokenHash) || @@ -195,7 +195,7 @@ private class BestFirstSearch private ( actualSplit.foreach { split => val nextState = curr.next(split, splitToken) val updateBest = !keepSlowStates && depth == 0 && - split.modification.isNewline && !best.contains(curr.depth) + split.isNL && !best.contains(curr.depth) if (updateBest) { best.update(curr.depth, nextState) } @@ -285,7 +285,7 @@ private class BestFirstSearch private ( if (activeSplits.isEmpty) null else state // dead end if empty else { val split = activeSplits.head - if (split.modification.isNewline) state + if (split.isNL) state else { runner.event(Enqueue(split)) val ft = tokens(state.depth) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala index 187a88885d..0325bac0b4 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala @@ -9,7 +9,7 @@ case class Decision(formatToken: FormatToken, splits: Seq[Split]) { import org.scalafmt.util.TokenOps._ def noNewlines: Seq[Split] = - splits.filterNot(_.modification.isNewline) + splits.filterNot(_.isNL) def onlyNewlinesWithFallback(default: => Split): Seq[Split] = { val filtered = onlyNewlineSplits @@ -26,7 +26,7 @@ case class Decision(formatToken: FormatToken, splits: Seq[Split]) { onlyNewlineSplits private def onlyNewlineSplits: Seq[Split] = - splits.filter(_.modification.isNewline) + splits.filter(_.isNL) def withSplits(splits: Seq[Split]): Decision = copy(splits = splits) 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 44e0ea1624..7d1550816d 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 @@ -389,9 +389,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { Policy(matching(tok.left)) { case Decision(t @ FormatToken(_, _: T.Comma, _), splits) if owner == t.meta.rightOwner && !next(t).right.is[T.Comment] => - splits.map { x => - if (x.modification != NoSplit) x else x.copy(modification = Newline) - } + splits.map(x => if (x.modExt.mod ne NoSplit) x else x.withMod(Newline)) case Decision(t @ FormatToken(_: T.Comma, right, _), splits) if owner == t.meta.leftOwner && @@ -399,7 +397,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { // If comment is bound to comma, see unit/Comment. (!right.is[T.Comment] || t.hasBreak) => val isNewline = right.is[T.Comment] - splits.filter(_.modification.isNewline == isNewline) + splits.filter(_.isNL == isNewline) } } @@ -417,7 +415,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { // If comment is bound to comma, see unit/Comment. (!right.is[T.Comment] || t.hasBreak) => if (!right.is[T.LeftBrace]) - splits.filter(_.modification.isNewline) + splits.filter(_.isNL) else if (!style.activeForEdition_2020_03) splits else @@ -447,7 +445,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { (penalizeLambdas || !tok.left.is[T.RightArrow]) && !ignore(tok) => s.map { case split - if split.modification.isNewline || + if split.isNL || (penaliseNewlinesInsideTokens && tok.leftHasNewline) => split.withPenalty(penalty) case x => x @@ -468,7 +466,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { nestedSelect(t.meta.leftOwner) + nestedApplies(t.meta.rightOwner) + nonBoolPenalty s.map { - case split if split.modification.isNewline => + case split if split.isNL => split.withPenalty(penalty) case x => x } @@ -660,7 +658,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { x => { val indent = Indent(Num(2), x.tokens.last, ExpiresOn.After) getInfixSplitsBeforeLhsOrRhs(app, ft, fullInfix).map { s => - if (s.modification.isNewline) s.withIndent(indent) else s + if (s.isNL) s.withIndent(indent) else s } } ) @@ -731,10 +729,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { if (isSingleLineComment(t.right)) // will break s.map(_.switch(firstInfixOp)) else - s.map { x => - if (!x.modification.isNewline) x - else x.switch(firstInfixOp) - } + s.map(x => if (x.isNL) x.switch(firstInfixOp) else x) } val singleLineExpire = if (isFirst) fullExpire else expires.head._1 @@ -799,7 +794,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { val exclude = if (breakMany) Set.empty[Range] else insideBlockRanges[LeftParenOrBrace](nextFT, expire) - Split(newStmtMod.getOrElse(Space), cost) + Split(ModExt(newStmtMod.getOrElse(Space)), cost) .withSingleLine(expire, exclude) } } @@ -1045,7 +1040,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { lastToken: Token, indentLen: Int )(implicit line: sourcecode.Line, style: ScalafmtConfig): Seq[Split] = { - val nlMod = NewlineT(acceptSpace = true) + val nlMod = NewlineT(alt = Some(Space)) val owners = chain.fold[Set[Tree]](Set(_), x => x.toSet) val nlPolicy = ctorWithChain(owners, lastToken) val nlOnelineTag = style.binPack.parentConstructors match { @@ -1081,7 +1076,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { private def unapplyImpl(d: Decision): Option[Seq[Split]] = { var replaced = false def decisionPf(s: Split): Split = - if (!s.modification.isNewline) s + if (!s.isNL) s else { replaced = true s.orElsePolicy(onBreakPolicy) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala index 2e5375f607..54bbee593d 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala @@ -89,7 +89,7 @@ class FormatWriter(formatOps: FormatOps) { val prev = state.prev val idx = prev.depth val ft = toks(idx) - val breaks = state.split.modification.isNewline || ft.leftHasNewline + val breaks = state.split.isNL || ft.leftHasNewline val newLineId = lineId + (if (breaks) 1 else 0) result(idx) = FormatLocation(ft, state, styleMap.at(ft), newLineId) iter(prev, newLineId) @@ -134,12 +134,12 @@ class FormatWriter(formatOps: FormatOps) { val inParentheses = loc.style.spaces.inParentheses // remove space before "{" val prevBegState = - if (0 == idx || (state.prev.split.modification ne Space)) + if (0 == idx || (state.prev.split.modExt.mod ne Space)) state.prev else { val prevloc = locations(idx - 1) - val prevState = state.prev - .copy(split = state.prev.split.copy(modification = NoSplit)) + val prevState = + state.prev.copy(split = state.prev.split.withMod(NoSplit)) locations(idx - 1) = prevloc.copy( shift = prevloc.shift - 1, state = prevState @@ -156,7 +156,7 @@ class FormatWriter(formatOps: FormatOps) { ) else { // remove space after "{" - val split = state.split.copy(modification = NoSplit) + val split = state.split.withMod(NoSplit) loc.copy( replace = "(", shift = loc.shift - 1, @@ -170,7 +170,7 @@ class FormatWriter(formatOps: FormatOps) { if (inParentheses) prevEndState else { // remove space before "}" - val split = prevEndState.split.copy(modification = NoSplit) + val split = prevEndState.split.withMod(NoSplit) val newState = prevEndState.copy(split = split) locations(end - 1) = prevEndLoc .copy(shift = prevEndLoc.shift - 1, state = newState) @@ -209,7 +209,7 @@ class FormatWriter(formatOps: FormatOps) { @inline def tok = curr.formatToken @inline def state = curr.state @inline def prevState = curr.state.prev - @inline def lastModification = prevState.split.modification + @inline def lastModification = prevState.split.modExt.mod def getWhitespace(alignOffset: Int): String = { // TODO this could get slow for really long comment blocks. If that @@ -220,7 +220,7 @@ class FormatWriter(formatOps: FormatOps) { if (0 > nonCommentIdx) None else Some(locations(nonCommentIdx)) } - state.split.modification match { + state.split.modExt.mod match { case Space => val previousAlign = if (lastModification != NoSplit) 0 @@ -228,15 +228,6 @@ class FormatWriter(formatOps: FormatOps) { val currentAlign = getAlign(tok, alignOffset) getIndentation(1 + currentAlign + previousAlign) - case nl: NewlineT - if nl.acceptNoSplit && !tok.left.isInstanceOf[T.Comment] && - state.indentation >= prevState.column => - "" - - case nl: NewlineT - if nl.acceptSpace && state.indentation >= prevState.column => - " " - case _: NewlineT if tok.right.isInstanceOf[T.Comment] && nextNonComment.exists( @@ -289,7 +280,7 @@ class FormatWriter(formatOps: FormatOps) { val right = nextNonCommentTok.right def isNewline = Seq(curr, locations(math.min(i + skip, locations.length - 1))) - .exists(_.state.split.modification.isNewline) + .exists(_.state.split.isNL) // Scala syntax allows commas before right braces in weird places, // like constructor bodies: @@ -356,8 +347,7 @@ class FormatWriter(formatOps: FormatOps) { case _: T.Constant.String => TreeOps.getStripMarginChar(tok.meta.leftOwner).map { pipe => def isPipeFirstChar = text.find(_ != '"').contains(pipe) - val noAlign = !style.align.stripMargin || - prevState.split.modification.isNewline + val noAlign = !style.align.stripMargin || prevState.split.isNL val pipeOffset = if (style.align.stripMargin && isPipeFirstChar) 1 else 0 val indent = pipeOffset + @@ -449,7 +439,7 @@ class FormatWriter(formatOps: FormatOps) { if (breakBefore) 0 else prevState.prev.column - prevState.prev.indentation + - prevState.split.modification.length + prevState.split.length protected final def canRewrite = style.comments.wrap match { @@ -808,7 +798,7 @@ class FormatWriter(formatOps: FormatOps) { def processLine: FormatLocation = { val floc = locations(idx) idx += 1 - val ok = !floc.state.split.modification.isNewline + val ok = !floc.state.split.isNL if (!ok || floc.formatToken.leftHasNewline) columnShift = 0 columnShift += floc.shift if (!ok) floc @@ -828,7 +818,7 @@ class FormatWriter(formatOps: FormatOps) { } implicit val location = processLine - val doubleNewline = location.state.split.modification.newlines > 1 + val doubleNewline = location.state.split.modExt.mod.newlines > 1 if (alignContainer eq null) { getBlockToFlush( getAlignContainer(location.formatToken.meta.rightOwner), @@ -1010,7 +1000,7 @@ class FormatWriter(formatOps: FormatOps) { private def shiftStateColumnIndent(startIdx: Int, offset: Int): Unit = { // look for StateColumn; it returns indent=0 for withStateOffset(0) - val stateIndentOpt = locations(startIdx).state.split.indents + val stateIndentOpt = locations(startIdx).state.split.modExt.indents .flatMap(_.withStateOffset(0).filter(_.length == 0)) stateIndentOpt.headOption.foreach { indent => @tailrec @@ -1018,7 +1008,7 @@ class FormatWriter(formatOps: FormatOps) { val floc = locations(idx) if (indent.notExpiredBy(floc.formatToken)) { val state = floc.state - if (state.split.modification.isNewline) { + if (state.split.isNL) { locations(idx) = floc.copy(state = state.copy(indentation = state.indentation + offset) ) @@ -1078,7 +1068,7 @@ class FormatWriter(formatOps: FormatOps) { if (minLines <= 0) true else if (i >= toks.length || toks(i).formatToken.left == end) false else { - val hasNL = toks(i).state.split.modification.isNewline + val hasNL = toks(i).state.split.isNL isMultiline(end, i + 1, if (hasNL) minLines - 1 else minLines) } val formatToken = toks(i).formatToken @@ -1238,9 +1228,9 @@ object FormatWriter { alignHashKey: Int = 0, replace: String = null ) { - def hasBreakAfter: Boolean = state.split.modification.isNewline + def hasBreakAfter: Boolean = state.split.isNL def hasBreakBefore: Boolean = - state.prev.split.modification.isNewline || formatToken.left.start == 0 + state.prev.split.isNL || formatToken.left.start == 0 def isStandalone: Boolean = hasBreakAfter && hasBreakBefore } 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 new file mode 100644 index 0000000000..e14773226c --- /dev/null +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala @@ -0,0 +1,55 @@ +package org.scalafmt.internal + +import scala.language.implicitConversions +import scala.meta.tokens.Token + +/** + * @param mod Is this a space, no space, newline or 2 newlines? + * @param indents Does this add indentation? + */ +case class ModExt( + mod: Modification, + indents: Seq[Indent] = Seq.empty +) { + lazy val indentation = indents.mkString("[", ", ", "]") + + def withIndent(length: => Length, expire: => Token, when: ExpiresOn): ModExt = + length match { + case Length.Num(0) => this + case x => withIndentImpl(Indent(x, expire, when)) + } + + def withIndentOpt( + length: => Length, + expire: Option[Token], + when: ExpiresOn + ): ModExt = + expire.fold(this)(withIndent(length, _, when)) + + def withIndent(indent: => Indent): ModExt = + indent match { + case Indent.Empty => this + case x => withIndentImpl(x) + } + + def withIndentOpt(indent: => Option[Indent]): ModExt = + indent.fold(this)(withIndent(_)) + + def withIndents(indents: Seq[Indent]): ModExt = + indents.foldLeft(this)(_ withIndent _) + + private def withIndentImpl(indent: Indent): ModExt = + copy(indents = indent +: indents) + + def switch(switchObject: AnyRef): ModExt = { + val newIndents = indents.map(_.switch(switchObject)) + copy(indents = newIndents.filter(_ ne Indent.Empty)) + } + +} + +object ModExt { + + implicit def implicitModToModExt(mod: Modification): ModExt = ModExt(mod) + +} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala index 1527bf5994..6b856d1bba 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala @@ -24,15 +24,15 @@ case object NoSplit extends Modification { * @param isDouble Insert a blank line? * @param noIndent Should no indentation follow? For example in commented out * code. - * @param acceptNoSplit Is it ok to replace this newline with a [[NoSplit]] - * if the newline will indent beyond the current column? - * For example, used by select chains in [[Router]]. + * @param alt + * Is it ok to replace this newline with a [[NoSplit]] or [[Space]] (with + * an optional additional set of indents) if the newline will indent beyond + * the current column? For example, used by select chains in [[Router]]. */ case class NewlineT( isDouble: Boolean = false, noIndent: Boolean = false, - acceptSpace: Boolean = false, - acceptNoSplit: Boolean = false + alt: Option[ModExt] = None ) extends Modification { override def toString = { val double = if (isDouble) "Double" else "" 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 df63904037..ce901f57c0 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 @@ -564,10 +564,7 @@ class Router(formatOps: FormatOps) { val forceNewlineBeforeExtends = Policy(expire) { case Decision(t @ FormatToken(_, _: T.KwExtends, _), s) if t.meta.rightOwner == leftOwner => - s.filter { x => - x.modification.isNewline && - (x.activeTag ne SplitTag.OnelineWithChain) - } + s.filter(x => x.isNL && (x.activeTag ne SplitTag.OnelineWithChain)) } val policyEnd = defnBeforeTemplate(leftOwner).fold(r)(_.tokens.last) val policy = delayedBreakPolicy(None)(forceNewlineBeforeExtends) @@ -982,7 +979,7 @@ class Router(formatOps: FormatOps) { val nlIndent = if (style.activeForEdition_2020_03) indent else Num(4) Seq( noSplit, - Split(NewlineT(acceptNoSplit = singleLineOnly), 2) + Split(NewlineT(alt = if (singleLineOnly) Some(NoSplit) else None), 2) .withIndent(nlIndent, close, Before) .withSingleLineOpt(if (singleLineOnly) Some(close) else None) .andThenPolicy(nlPolicy) @@ -1215,12 +1212,12 @@ class Router(formatOps: FormatOps) { Policy(tree.name.tokens.head) { case Decision(t @ FormatToken(_, _: T.Dot, _), s) if t.meta.rightOwner eq tree => - s.filter(_.modification.isNewline) + s.filter(_.isNL) } } Seq( Split(NoSplit, 0).withSingleLine(expire, noSyntaxNL = true), - Split(NewlineT(acceptNoSplit = true), 1) + Split(NewlineT(alt = Some(NoSplit)), 1) .withPolicyOpt(forcedBreakPolicy) ) } @@ -1230,7 +1227,7 @@ class Router(formatOps: FormatOps) { def exclude = insideBlockRanges[LeftParenOrBrace](t, end) Seq( Split(NoSplit, 0).withSingleLine(end, exclude), - Split(NewlineT(acceptNoSplit = true), 1) + Split(NewlineT(alt = Some(NoSplit)), 1) ) } @@ -1247,7 +1244,7 @@ class Router(formatOps: FormatOps) { val willBreak = nextNonCommentSameLine(tokens(formatToken, 2)).right.is[T.Comment] val splits = baseSplits.map { s => - if (willBreak || s.modification.isNewline) s.withIndent(indent) + if (willBreak || s.isNL) s.withIndent(indent) else s.andThenPolicyOpt(delayedBreakPolicy) } @@ -1267,11 +1264,21 @@ class Router(formatOps: FormatOps) { if (chain.length == 1) lastToken(chain.last) else optimalToken + def getNewline(ft: FormatToken): NewlineT = { + val (_, nextSelect) = findLastApplyAndNextSelect(ft.meta.rightOwner) + val endSelect = nextSelect.fold(optimalToken)(x => lastToken(x.qual)) + val nlAlt = ModExt(NoSplit).withIndent(-2, endSelect, After) + NewlineT(alt = Some(nlAlt)) + } + val breakOnEveryDot = Policy(expire) { case Decision(t @ FormatToken(_, _: T.Dot, _), _) if chain.contains(t.meta.rightOwner) => - val noNL = style.optIn.breaksInsideChains && t.noBreak - Seq(Split(NoSplit.orNL(noNL), 1)) + val mod = + if (style.optIn.breaksInsideChains) + NoSplit.orNL(t.noBreak) + else getNewline(t) + Seq(Split(mod, 1)) } val exclude = getExcludeIf(expire) // This policy will apply to both the space and newline splits, otherwise @@ -1307,7 +1314,7 @@ class Router(formatOps: FormatOps) { Split(NoSplit, 0) .notIf(ignoreNoSplit) .withPolicy(noSplitPolicy), - Split(NewlineT(acceptNoSplit = !ignoreNoSplit), nlCost) + Split(if (ignoreNoSplit) Newline else getNewline(tok), nlCost) .withPolicy(newlinePolicy) .withIndent(2, optimalToken, After) ) @@ -1884,15 +1891,12 @@ class Router(formatOps: FormatOps) { // TODO(olafur) refactor into "global policy" // Only newlines after inline comments. case FormatToken(c: T.Comment, _, _) if isSingleLineComment(c) => - val newlineSplits = splits.filter(_.modification.isNewline) + val newlineSplits = splits.filter(_.isNL) if (newlineSplits.isEmpty) Seq(Split(Newline, 0)) else newlineSplits case FormatToken(_, c: T.Comment, _) if isAttachedSingleLineComment(formatToken) => - splits.map(x => - if (x.modification.isNewline) x.copy(modification = Space)(x.line) - else x - ) + splits.map(x => if (x.isNL) x.withMod(Space) else x) case _ => splits } } 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 9f29e89069..879b1837b1 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 @@ -26,36 +26,38 @@ case class OptimalToken(token: Token, killOnFail: Boolean = false) * even if it exceeds the maxColumn margins, because a secondary split * was deemed unlikely to win and moved to a backup priority queue. * - * @param modification Is this a space, no space, newline or 2 newlines? + * @param modExt whitespace and indents * @param cost How good is this output? Lower is better. - * @param indents Does this add indentation? * @param policy How does this split affect other later splits? * @param line For debugging, to retrace from which case in [[Router]] * this split originates. * */ case class Split( - modification: Modification, + modExt: ModExt, cost: Int, tag: SplitTag = SplitTag.Active, activeTag: SplitTag = SplitTag.Active, - indents: Seq[Indent] = Seq.empty, policy: Policy = NoPolicy, optimalAt: Option[OptimalToken] = None )(implicit val line: sourcecode.Line) { import TokenOps._ def adapt(formatToken: FormatToken): Split = - modification match { + modExt.mod match { case n: NewlineT if !n.noIndent && rhsIsCommentedOut(formatToken) => - copy(modification = NewlineT(n.isDouble, noIndent = true)) + copy(modExt = modExt.copy(mod = NewlineT(n.isDouble, noIndent = true))) case _ => this } - val indentation = indents.mkString("[", ", ", "]") + @inline + def indentation: String = modExt.indentation + + @inline + def isNL: Boolean = modExt.mod.isNewline @inline - def length: Int = modification.length + def length: Int = modExt.mod.length @inline def isIgnored: Boolean = tag eq SplitTag.Ignored @@ -165,42 +167,36 @@ case class Split( if (isIgnored) this else copy(cost = cost + penalty) def withIndent(length: => Length, expire: => Token, when: ExpiresOn): Split = - if (isIgnored) this - else - length match { - case Length.Num(0) => this - case x => withIndentImpl(Indent(x, expire, when)) - } + withMod(modExt.withIndent(length, expire, when)) def withIndentOpt( length: => Length, expire: Option[Token], when: ExpiresOn ): Split = - expire.fold(this)(withIndent(length, _, when)) + withMod(modExt.withIndentOpt(length, expire, when)) def withIndent(indent: => Indent): Split = - if (isIgnored) this - else - indent match { - case Indent.Empty => this - case x => withIndentImpl(x) - } + withMod(modExt.withIndent(indent)) def withIndentOpt(indent: => Option[Indent]): Split = - if (isIgnored) this - else indent.fold(this)(withIndent(_)) + withMod(modExt.withIndentOpt(indent)) def withIndents(indents: Seq[Indent]): Split = - indents.foldLeft(this)(_ withIndent _) + withMod(modExt.withIndents(indents)) - private def withIndentImpl(indent: Indent): Split = - copy(indents = indent +: indents) + def switch(switchObject: AnyRef): Split = + withMod(modExt.switch(switchObject)) - def switch(switchObject: AnyRef): Split = { - val newIndents = indents.map(_.switch(switchObject)) - copy(indents = newIndents.filter(_ ne Indent.Empty)) - } + def withMod(mod: Modification): Split = + if (this.modExt.mod eq mod) this else withMod(modExt.copy(mod = mod)) + + def withMod(modExtByName: => ModExt): Split = + if (isIgnored) this + else { + val modExt = modExtByName + if (this.modExt eq modExt) this else copy(modExt = modExt)(line = line) + } override def toString = { val prefix = tag match { @@ -208,13 +204,13 @@ case class Split( case SplitTag.Active => "" case _ => s"[$tag]" } - s"""$prefix$modification:${line.value}(cost=$cost, indents=$indentation, $policy)""" + s"""$prefix${modExt.mod}:${line.value}(cost=$cost, indents=$indentation, $policy)""" } } object Split { def ignored(implicit line: sourcecode.Line) = - Split(NoSplit, 0, tag = SplitTag.Ignored) + Split(ModExt(NoSplit), 0, tag = SplitTag.Ignored) } 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 b77bdbb285..7b7c49aad7 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 @@ -19,7 +19,7 @@ final case class State( depth: Int, prev: State, indentation: Int, - pushes: Vector[ActualIndent], + pushes: Seq[ActualIndent], column: Int, formatOff: Boolean ) { @@ -33,39 +33,58 @@ final case class State( * Calculates next State given split at tok. */ def next( - split: Split, + initialNextSplit: Split, tok: FormatToken )(implicit style: ScalafmtConfig): State = { val right = tok.right val tokRightSyntax = tok.meta.right.text - val newIndents: Vector[ActualIndent] = - if (right.is[Token.EOF]) Vector.empty + val (nextSplit, nextIndent, nextIndents) = + if (tok.right.is[Token.EOF]) (initialNextSplit, 0, Seq.empty) else { val offset = column - indentation - val newPushes = split.indents.flatMap(_.withStateOffset(offset)) - (pushes ++ newPushes).filter(_.notExpiredBy(tok)) + def getIndent(indents: Iterator[ActualIndent]): Int = + indents.foldLeft(0)(_ + _.length) + def getUnexpired(indents: Seq[ActualIndent]): Seq[ActualIndent] = + indents.filter(_.notExpiredBy(tok)) + def getPushes(indents: Seq[Indent]): Seq[ActualIndent] = + getUnexpired(indents.flatMap(_.withStateOffset(offset))) + val indents = initialNextSplit.modExt.indents + val nextPushes = getUnexpired(pushes) ++ getPushes(indents) + val nextIndent = getIndent(nextPushes.iterator) + initialNextSplit.modExt.mod match { + case m: NewlineT + if !tok.left.is[Token.Comment] && m.alt.isDefined && + nextIndent >= m.alt.get.mod.length + column => + val alt = m.alt.get + val altPushes = getPushes(alt.indents) + val altIndent = getIndent(altPushes.iterator) + val split = initialNextSplit.withMod(alt.withIndents(indents)) + (split, nextIndent + altIndent, nextPushes ++ altPushes) + case _ => + (initialNextSplit, nextIndent, nextPushes) + } } - val newIndent = newIndents.foldLeft(0)(_ + _.length) // Some tokens contain newline, like multiline strings/comments. val (columnOnCurrentLine, nextStateColumn) = State.getColumns( tok, - newIndent, - if (split.modification.isNewline) None else Some(column + split.length) + nextIndent, + if (nextSplit.isNL) None else Some(column + nextSplit.length) ) - val newPolicy: PolicySummary = policy.combine(split.policy, tok.left.end) + val nextPolicy: PolicySummary = + policy.combine(nextSplit.policy, tok.left.end) val splitWithPenalty = { if ( columnOnCurrentLine <= style.maxColumn || { val commentExceedsLineLength = right.is[Token.Comment] && - tokRightSyntax.length >= (style.maxColumn - newIndent) - commentExceedsLineLength && split.modification.isNewline + tokRightSyntax.length >= (style.maxColumn - nextIndent) + commentExceedsLineLength && nextSplit.isNL } ) { - split // fits inside column + nextSplit // fits inside column } else { - split.withPenalty( + nextSplit.withPenalty( Constants.ExceedColumnPenalty + columnOnCurrentLine ) // overflow } @@ -77,12 +96,12 @@ final case class State( State( cost + splitWithPenalty.cost, // TODO(olafur) expire policy, see #18. - newPolicy, + nextPolicy, splitWithPenalty, depth + 1, this, - newIndent, - newIndents, + nextIndent, + nextIndents, nextStateColumn, nextFormatOff ) @@ -99,7 +118,7 @@ object State { 0, null, 0, - Vector.empty[ActualIndent], + Seq.empty, 0, formatOff = false ) diff --git a/scalafmt-tests/src/test/resources/default/Apply.stat b/scalafmt-tests/src/test/resources/default/Apply.stat index ae9814e90c..e583706453 100644 --- a/scalafmt-tests/src/test/resources/default/Apply.stat +++ b/scalafmt-tests/src/test/resources/default/Apply.stat @@ -1003,9 +1003,226 @@ object a { "mod", Seq(common, db, user, hub, security, game, analyse, evaluation, report) ).settings( - libraryDependencies ++= provided(play.api, play.test, RM, PRM) - ) - .settings( - libraryDependencies ++= provided(play.api, play.test, RM, PRM) - ) + libraryDependencies ++= provided(play.api, play.test, RM, PRM) + ).settings( + libraryDependencies ++= provided(play.api, play.test, RM, PRM) + ) +} +<<< #1400, breaksInsideChains T, noBB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = true +=== +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + } + .getOrElse{ // break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + } + .getOrElse { // break + "something3" + } +} +<<< #1400, breaksInsideChains T, noBnoB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = true +=== +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + }.getOrElse{ // no break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + }.getOrElse { // no break + "something3" + } +} +<<< #1400, breaksInsideChains T, BB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = true +=== +object Test { + Try { + "something1" + } + .recover { // break + case NonFatal(_) => "something2" + } + .getOrElse{ // break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // break + case NonFatal(_) => "something2" + } + .getOrElse { // break + "something3" + } +} +<<< #1400, breaksInsideChains T, BnoB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = true +=== +object Test { + Try { + "something1" + } + .recover { // break + case NonFatal(_) => "something2" + }.getOrElse{ // no break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // break + case NonFatal(_) => "something2" + }.getOrElse { // no break + "something3" + } +} +<<< #1400, breaksInsideChains F, noBB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = false +=== +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + } + .getOrElse{ // break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + }.getOrElse { // break + "something3" + } +} +<<< #1400, breaksInsideChains F, noBnoB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = false +=== +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + }.getOrElse{ // no break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // no break + case NonFatal(_) => "something2" + }.getOrElse { // no break + "something3" + } +} +<<< #1400, breaksInsideChains F, BB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = false +=== +object Test { + Try { + "something1" + } + .recover { // break + case NonFatal(_) => "something2" + } + .getOrElse{ // break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // break + case NonFatal(_) => "something2" + }.getOrElse { // break + "something3" + } +} +<<< #1400, breaksInsideChains F, BnoB +preset = intellij +danglingParentheses.preset = true +optIn.configStyleArguments = true +optIn.breakChainOnFirstMethodDot = false +optIn.breaksInsideChains = false +=== +object Test { + Try { + "something1" + } + .recover { // break + case NonFatal(_) => "something2" + }.getOrElse{ // no break + "something3" + } +} +>>> +object Test { + Try { + "something1" + }.recover { // break + case NonFatal(_) => "something2" + }.getOrElse { // no break + "something3" + } } diff --git a/scalafmt-tests/src/test/resources/default/Lambda.stat b/scalafmt-tests/src/test/resources/default/Lambda.stat index 2a2f1756f9..d78a470cf0 100644 --- a/scalafmt-tests/src/test/resources/default/Lambda.stat +++ b/scalafmt-tests/src/test/resources/default/Lambda.stat @@ -437,21 +437,20 @@ class A { class A { def foo: B = a.b() { c => - val d = c.add( - D[E]( - 1, - { - case conditionNameShouldLongEnough => - foo1(objectNameShouldLongEnough, objectNameShouldLongEnough) - 0 - case conditionNameShouldLongEnough => - foo1(objectNameShouldLongEnough, objectNameShouldLongEnough) - 1 - } - ) + val d = c.add( + D[E]( + 1, + { + case conditionNameShouldLongEnough => + foo1(objectNameShouldLongEnough, objectNameShouldLongEnough) + 0 + case conditionNameShouldLongEnough => + foo1(objectNameShouldLongEnough, objectNameShouldLongEnough) + 1 + } ) - } - .f("f") + ) + }.f("f") } <<< #1969 object a {