From 7e4f8d328fef4ba67e1e9a5118bea61190ca0256 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Thu, 2 Jul 2020 21:57:09 -0700 Subject: [PATCH 1/8] Add a test for config style in vertical multiline --- .../VerticalMultilineDefnSite.stat | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat b/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat index 39d1140f28..4b1a0895b4 100644 --- a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat +++ b/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat @@ -39,3 +39,21 @@ object Test { } } +<<< #2044 +verticalMultiline.excludeDanglingParens = [] +=== +object a { + final class Dummy[F[+_, +_]: BIOMonad: BIOPrimitives: Clock2]( + log: LogBIO[F] + ) extends DIResource.LiftF( + F.mkRef(Map.empty[Key, Item]).map(new Dummy.Impl(_)) + ) +} +>>> +object a { + final class Dummy[F[+_, +_]: BIOMonad: BIOPrimitives: Clock2]( + log: LogBIO[F] + ) extends DIResource.LiftF(F + .mkRef(Map.empty[Key, Item]) + .map(new Dummy.Impl(_))) +} From 8519a2acacdc23458bee5e392104bc8094d471cf Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:49:49 -0700 Subject: [PATCH 2/8] FormatOps: use Policy, not Policy partial function --- .../org/scalafmt/internal/FormatOps.scala | 49 +++++++++++-------- .../scala/org/scalafmt/internal/Router.scala | 32 ++++++------ 2 files changed, 45 insertions(+), 36 deletions(-) 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 4fcb426418..f19683abb0 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 @@ -770,7 +770,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { val breakAfterClose = endOfNextOp.flatMap { tok => val end = nextNonCommentSameLine(tokens(tok)) if (end.right.is[T.LeftBrace]) None - else Some(Policy.map(decideNewlinesOnlyAfterToken)(end.left)) + else Some(decideNewlinesOnlyAfterToken(end.left)) } val nlSplit = Split(nlMod, 0) @@ -1090,25 +1090,34 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { else onBreakPolicy.copy(f = { case OnBreakDecision(d) => d }) } - def newlinesOnlyBeforeClosePolicy(close: Token)(implicit - line: sourcecode.Line - ): Policy = - Policy.map(decideNewlinesOnlyBeforeClose(Split(Newline, 0)))(close) - - def decideNewlinesOnlyBeforeClose(split: Split)(close: Token): Policy.Pf = { - case d: Decision if d.formatToken.right eq close => - d.onlyNewlinesWithFallback(split) - } + def decideNewlinesOnlyBeforeClose( + close: Token + )(implicit line: sourcecode.Line): Policy = + decideNewlinesOnlyBeforeClose(Split(Newline, 0))(close) + + def decideNewlinesOnlyBeforeClose( + split: Split + )(close: Token)(implicit line: sourcecode.Line): Policy = + Policy(close) { + case d: Decision if d.formatToken.right eq close => + d.onlyNewlinesWithFallback(split) + } - def decideNewlinesOnlyAfterClose(split: Split)(close: Token): Policy.Pf = { - case d: Decision if d.formatToken.left eq close => - d.onlyNewlinesWithFallback(split) - } + def decideNewlinesOnlyAfterClose( + split: Split + )(close: Token)(implicit line: sourcecode.Line): Policy = + Policy(close) { + case d: Decision if d.formatToken.left eq close => + d.onlyNewlinesWithFallback(split) + } - def decideNewlinesOnlyAfterToken(token: Token): Policy.Pf = { - case d: Decision if d.formatToken.left eq token => - d.onlyNewlinesWithoutFallback - } + def decideNewlinesOnlyAfterToken( + token: Token + )(implicit line: sourcecode.Line): Policy = + Policy(token) { + case d: Decision if d.formatToken.left eq token => + d.onlyNewlinesWithoutFallback + } def getForceConfigStyle: (Set[Tree], Set[TokenHash]) = { val maxDistance = runner.optimizer.forceConfigStyleOnOffset @@ -1209,7 +1218,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { rpOwner == owner || rpOwner == valueParamsOwner } - val paramGroupSplitter: Policy.Pf = { + val paramGroupSplitter = Policy(lastParen) { // If this is a class, then don't dangle the last paren unless the line ends with a comment case Decision(t @ FormatToken(previous, rp @ RightParenOrBracket(), _), _) if shouldNotDangle && rp == lastParen && !isSingleLineComment( @@ -1250,7 +1259,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { // Our policy is a combination of OneArgLineSplit and a custom splitter // for parameter groups. - val policy = oneLinePerArg.orElse(paramGroupSplitter, lastParen.end) + val policy = oneLinePerArg.orElse(paramGroupSplitter) val firstIndent = if (r.is[T.RightParen]) // An empty param group 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 8a440d6561..bc5102e0dd 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 @@ -131,7 +131,7 @@ class Router(formatOps: FormatOps) { close, disallowSingleLineComments = disallowSingleLineComments ) - val newlineBeforeClosingCurly = newlinesOnlyBeforeClosePolicy(close) + val newlineBeforeClosingCurly = decideNewlinesOnlyBeforeClose(close) val newlinePolicy = style.importSelectors match { case ImportSelectors.noBinPack => @@ -166,7 +166,7 @@ class Router(formatOps: FormatOps) { case tok @ FormatToken(open @ T.LeftBrace(), right, between) => val close = matching(open) val closeFT = tokens(close) - val newlineBeforeClosingCurly = newlinesOnlyBeforeClosePolicy(close) + val newlineBeforeClosingCurly = decideNewlinesOnlyBeforeClose(close) val selfAnnotation: Option[Tokens] = leftOwner match { // Self type: trait foo { self => ... } case t: Template => Some(t.self.name.tokens).filter(_.nonEmpty) @@ -210,7 +210,7 @@ class Router(formatOps: FormatOps) { def getSingleLineDecisionPre2019NovOpt = leftOwner.parent match { case Some(_: Term.If | _: Term.Try | _: Term.TryWithHandler) => None - case _ => Some(Policy.emptyPf) + case _ => Some(Policy.NoPolicy) } def getSingleLineDecisionFor2019Nov = { type Classifiers = Seq[Classifier[Token, _]] @@ -233,7 +233,7 @@ class Router(formatOps: FormatOps) { val afterClose = closeFT.right classifiers.exists(_(afterClose)) } - if (!breakSingleLineAfterClose) Policy.emptyPf + if (!breakSingleLineAfterClose) Policy.NoPolicy else decideNewlinesOnlyAfterClose(Split(Newline, 0))(close) } def getClassicSingleLineDecisionOpt = @@ -287,7 +287,7 @@ class Router(formatOps: FormatOps) { val expire = if (useOpt) endOfSingleLineBlock(closeFT) else close Split(xmlSpace(leftOwner), 0) .withSingleLine(expire, noSyntaxNL = true, killOnFail = true) - .andThenPolicy(Policy(expire)(sld)) + .andThenPolicy(sld) } val splits = Seq( @@ -613,7 +613,7 @@ class Router(formatOps: FormatOps) { val close = matching(formatToken.left) val newlinePolicy = if (!style.danglingParentheses.callSite) None - else Some(newlinesOnlyBeforeClosePolicy(close)) + else Some(decideNewlinesOnlyBeforeClose(close)) val spacePolicy = SingleLineBlock(lambdaToken).orElse { if (lambdaIsABlock) None else @@ -749,9 +749,9 @@ class Router(formatOps: FormatOps) { val newlinePolicy: Policy = if (wouldDangle || mustDangle) { - newlinesOnlyBeforeClosePolicy(close) + decideNewlinesOnlyBeforeClose(close) } else { - Policy.empty(close) + Policy.NoPolicy } val handleImplicit = @@ -790,7 +790,7 @@ class Router(formatOps: FormatOps) { } val breakToken = getOptimalTokenFor(assignToken) val newlineAfterAssignDecision = - if (newlinePolicy.isEmpty) Policy.emptyPf + if (newlinePolicy.isEmpty) Policy.NoPolicy else decideNewlinesOnlyAfterToken(breakToken) Seq( Split(Newline, nestedPenalty + Constants.ExceedColumnPenalty) @@ -813,7 +813,7 @@ class Router(formatOps: FormatOps) { Indent(Num(2), right, After) else Indent.Empty val (implicitPenalty, implicitPolicy) = - if (!handleImplicit) (2, Policy.emptyPf) + if (!handleImplicit) (2, Policy.NoPolicy) else (0, decideNewlinesOnlyAfterToken(right)) val splitsNoNL = @@ -920,7 +920,7 @@ class Router(formatOps: FormatOps) { if (right.is[T.Comment]) getMod(formatToken) else NoSplit val nlDanglePolicy = - if (mustDangle) newlinesOnlyBeforeClosePolicy(close) else NoPolicy + if (mustDangle) decideNewlinesOnlyBeforeClose(close) else NoPolicy Seq( Split(noSplitModification, 0 + (nestingPenalty * bracketCoef)) @@ -973,7 +973,7 @@ class Router(formatOps: FormatOps) { baseNoSplit.withOptimalTokenOpt(opt).withPolicy(policy) } - def newlineBeforeClose = newlinesOnlyBeforeClosePolicy(close) + def newlineBeforeClose = decideNewlinesOnlyBeforeClose(close) val nlPolicy = if (onlyConfigStyle) { if (styleMap.forcedBinPack(leftOwner)) newlineBeforeClose @@ -1060,7 +1060,7 @@ class Router(formatOps: FormatOps) { val breakAfter = rhsOptimalToken(next(nextNonCommentSameLine(formatToken))) val multiLine = - newlinesOnlyBeforeClosePolicy(close) + decideNewlinesOnlyBeforeClose(close) .orElse(decideNewlinesOnlyAfterToken(breakAfter)) Seq( Split(Newline, 0).withSingleLine(close, killOnFail = true), @@ -1441,7 +1441,7 @@ class Router(formatOps: FormatOps) { Split(Newline, 1) .withIndent(style.continuationIndent.callSite, close, Before) .withPolicy(penalizeNewlines) - .andThenPolicy(newlinesOnlyBeforeClosePolicy(close)) + .andThenPolicy(decideNewlinesOnlyBeforeClose(close)) ) else { val indent: Length = @@ -1562,7 +1562,7 @@ class Router(formatOps: FormatOps) { editionActive && style.danglingParentheses.callSite val policy = if (!shouldDangle) NoPolicy - else newlinesOnlyBeforeClosePolicy(close) + else decideNewlinesOnlyBeforeClose(close) Split(Newline, cost) .withPolicy(policy) .withIndent(style.continuationIndent.callSite, close, Before) @@ -2117,7 +2117,7 @@ class Router(formatOps: FormatOps) { case _: Term.ForYield => // unfold policy on yield forces a break // revert it if we are attempting a single line - val noBreakOnYield: Policy.Pf = { + val noBreakOnYield = Policy(expire) { case Decision(ft, s) if s.isEmpty && ft.right.is[Token.KwYield] => Seq(Split(Space, 0)) } From 4e9f8674067922f7ea7a913d7274347077c190b0 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Thu, 2 Jul 2020 07:58:16 -0700 Subject: [PATCH 3/8] Policy: use '&' and '|' instead of orElse/andThen --- .../org/scalafmt/internal/FormatOps.scala | 16 ++--- .../scala/org/scalafmt/internal/Policy.scala | 8 +-- .../scala/org/scalafmt/internal/Router.scala | 58 +++++++++---------- .../scala/org/scalafmt/internal/Split.scala | 30 +++++----- 4 files changed, 58 insertions(+), 54 deletions(-) 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 f19683abb0..68a6dde004 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 @@ -741,11 +741,11 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { .onlyIf(singleLinePolicy.isDefined && beforeLhs) .withIndent(nlIndentLength, singleLineExpire, ExpiresOn.After) .withSingleLine(singleLineExpire) - .andThenPolicyOpt(singleLinePolicy) + .andPolicyOpt(singleLinePolicy) val spaceSingleLine = Split(Space, 0) .onlyIf(newStmtMod.isEmpty) .withSingleLine(singleLineExpire) - .andThenPolicyOpt(singleLinePolicy) + .andPolicyOpt(singleLinePolicy) val singleLineSplits = Seq( spaceSingleLine.onlyFor(SplitTag.InfixChainNoNL), spaceSingleLine.onlyIf(singleLinePolicy.isDefined), @@ -774,14 +774,14 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { } val nlSplit = Split(nlMod, 0) - .andThenPolicyOpt(breakAfterClose) + .andPolicyOpt(breakAfterClose) .withIndent(nlIndent) .withPolicy(nlPolicy) val singleLineSplit = Split(Space, 0) .notIf(noSingleLine) .withSingleLine(endOfNextOp.getOrElse(close)) - .andThenPolicyOpt(breakAfterClose) - .andThenPolicy(getSingleLineInfixPolicy(close)) + .andPolicyOpt(breakAfterClose) + .andPolicy(getSingleLineInfixPolicy(close)) Seq(singleLineSplit, nlSplit) } @@ -1080,7 +1080,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { if (!s.isNL) s else { replaced = true - s.orElsePolicy(onBreakPolicy) + s.orPolicy(onBreakPolicy) } val splits = d.splits.map(decisionPf) if (replaced) Some(splits) else None @@ -1202,7 +1202,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { // a class with type AND value params. Otherwise it is a class with // just type params. findFirst(afterTypes, lastParen)(t => t.left.is[T.LeftParen]) - .fold(base)(t => base.orElse(OneArgOneLineSplit(t))) + .fold(base)(t => base | OneArgOneLineSplit(t)) } else base } @@ -1259,7 +1259,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { // Our policy is a combination of OneArgLineSplit and a custom splitter // for parameter groups. - val policy = oneLinePerArg.orElse(paramGroupSplitter) + val policy = oneLinePerArg | paramGroupSplitter val firstIndent = if (r.is[T.RightParen]) // An empty param group 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 ce8b965fee..3d7637cd81 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 @@ -26,13 +26,13 @@ case class Policy( expire = math.max(minExpire, expire) ) - def orElse(other: Policy): Policy = + def |(other: Policy): Policy = orElse(other.f, other.expire) - def orElse(other: Option[Policy]): Policy = - other.fold(this)(orElse) + def |(other: Option[Policy]): Policy = + other.fold(this)(|) - def andThen(other: Policy): Policy = + def &(other: Policy): Policy = if (isEmpty) other else andThen(other.f, other.expire) /** Similar to PartialFunction.andThen, except applies second pf even if the 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 bc5102e0dd..ab5ee75842 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 @@ -135,7 +135,7 @@ class Router(formatOps: FormatOps) { val newlinePolicy = style.importSelectors match { case ImportSelectors.noBinPack => - newlineBeforeClosingCurly.andThen(OneArgOneLineSplit(formatToken)) + newlineBeforeClosingCurly & OneArgOneLineSplit(formatToken) case ImportSelectors.binPack => newlineBeforeClosingCurly case ImportSelectors.singleLine => @@ -202,9 +202,9 @@ class Router(formatOps: FormatOps) { if (lambdaExpire == null) null else { val arrowOptimal = getOptimalTokenFor(lambdaExpire) - newlineBeforeClosingCurly - .andThen(SingleLineBlock(arrowOptimal)) - .andThen(decideNewlinesOnlyAfterToken(arrowOptimal)) + newlineBeforeClosingCurly & + SingleLineBlock(arrowOptimal) & + decideNewlinesOnlyAfterToken(arrowOptimal) } def getSingleLineDecisionPre2019NovOpt = @@ -287,7 +287,7 @@ class Router(formatOps: FormatOps) { val expire = if (useOpt) endOfSingleLineBlock(closeFT) else close Split(xmlSpace(leftOwner), 0) .withSingleLine(expire, noSyntaxNL = true, killOnFail = true) - .andThenPolicy(sld) + .andPolicy(sld) } val splits = Seq( @@ -614,7 +614,7 @@ class Router(formatOps: FormatOps) { val newlinePolicy = if (!style.danglingParentheses.callSite) None else Some(decideNewlinesOnlyBeforeClose(close)) - val spacePolicy = SingleLineBlock(lambdaToken).orElse { + val spacePolicy = SingleLineBlock(lambdaToken) | { if (lambdaIsABlock) None else newlinePolicy.map( @@ -798,16 +798,14 @@ class Router(formatOps: FormatOps) { .withIndent(indent, close, Before), Split(NoSplit, nestedPenalty) .withSingleLine(breakToken) - .andThenPolicy( - newlinePolicy.andThen(newlineAfterAssignDecision) - ) + .andPolicy(newlinePolicy & newlineAfterAssignDecision) ) } val preferNoSplit = singleArgument && style.newlines.sourceIs(Newlines.keep) && tok.noBreak val oneArgOneLine = - newlinePolicy.andThen(OneArgOneLineSplit(formatToken)) + newlinePolicy & OneArgOneLineSplit(formatToken) val extraOneArgPerLineIndent = if (multipleArgs && style.poorMansTrailingCommasInConfigStyle) Indent(Num(2), right, After) @@ -821,7 +819,7 @@ class Router(formatOps: FormatOps) { else if (onlyConfigStyle) Seq( Split(noSplitMod, 0) - .withPolicy(oneArgOneLine.andThen(implicitPolicy)) + .withPolicy(oneArgOneLine & implicitPolicy) .withOptimalToken(right, killOnFail = true) .withIndent(extraOneArgPerLineIndent) .withIndent(indent, close, Before) @@ -840,7 +838,7 @@ class Router(formatOps: FormatOps) { .withOptimalToken(expirationToken) .withIndent(noSplitIndent, close, Before), Split(noSplitMod, (implicitPenalty + lhsPenalty) * bracketCoef) - .withPolicy(oneArgOneLine.andThen(implicitPolicy)) + .withPolicy(oneArgOneLine & implicitPolicy) .onlyIf( (notTooManyArgs && align) || (handleImplicit && style.newlines.notBeforeImplicitParamListModifier) @@ -871,7 +869,7 @@ class Router(formatOps: FormatOps) { val noConfigStyle = noSplit || newlinePolicy.isEmpty || !style.optIn.configStyleArguments Split(NoSplit.orNL(noSplit), cost, policy = newlinePolicy) - .andThenPolicy(singleLine(4), !noConfigStyle) + .andPolicy(singleLine(4), !noConfigStyle) } Seq(split.withIndent(indent, close, Before)) } @@ -909,7 +907,7 @@ class Router(formatOps: FormatOps) { case Some(arg) => val singleLine = SingleLineBlock(arg.tokens.last) if (isBracket) { - noSplitPenalizeNewlines.andThen(singleLine.f) + noSplitPenalizeNewlines & singleLine } else { singleLine } @@ -930,7 +928,7 @@ class Router(formatOps: FormatOps) { Split(Newline, (1 + nestingPenalty * nestingPenalty) * bracketCoef) .notIf(right.is[T.RightParen]) .withPolicy(penalizeBrackets(1)) - .andThenPolicy(nlDanglePolicy) + .andPolicy(nlDanglePolicy) .withIndent(indent, close, Before) ) } @@ -963,13 +961,12 @@ class Router(formatOps: FormatOps) { insideBlock[T.LeftBrace](formatToken, close) val excludeRanges: Set[Range] = exclude.map((matchingParensRange _).tupled).toSet - val unindent = UnindentAtExclude(exclude.keySet, Num(-indent.n)) def ignoreBlocks(x: FormatToken): Boolean = { excludeRanges.exists(_.contains(x.left.end)) } val policy = - penalizeAllNewlines(close, 3, ignore = ignoreBlocks) - .andThen(unindent) + penalizeAllNewlines(close, 3, ignore = ignoreBlocks) & + Policy(close)(UnindentAtExclude(exclude.keySet, Num(-indent.n))) baseNoSplit.withOptimalTokenOpt(opt).withPolicy(policy) } @@ -977,7 +974,7 @@ class Router(formatOps: FormatOps) { val nlPolicy = if (onlyConfigStyle) { if (styleMap.forcedBinPack(leftOwner)) newlineBeforeClose - else OneArgOneLineSplit(formatToken).orElse(newlineBeforeClose) + else OneArgOneLineSplit(formatToken) | newlineBeforeClose } else if ( style.newlines.sourceIgnored && style.danglingParentheses.callSite @@ -990,7 +987,7 @@ class Router(formatOps: FormatOps) { Split(NewlineT(alt = if (singleLineOnly) Some(NoSplit) else None), 2) .withIndent(nlIndent, close, Before) .withSingleLineOpt(if (singleLineOnly) Some(close) else None) - .andThenPolicy(nlPolicy) + .andPolicy(nlPolicy) ) // If configured to skip the trailing space after `if` and other keywords, do so. @@ -1060,8 +1057,8 @@ class Router(formatOps: FormatOps) { val breakAfter = rhsOptimalToken(next(nextNonCommentSameLine(formatToken))) val multiLine = - decideNewlinesOnlyBeforeClose(close) - .orElse(decideNewlinesOnlyAfterToken(breakAfter)) + decideNewlinesOnlyBeforeClose(close) | + decideNewlinesOnlyAfterToken(breakAfter) Seq( Split(Newline, 0).withSingleLine(close, killOnFail = true), Split(Space, 1, policy = multiLine) @@ -1253,7 +1250,7 @@ class Router(formatOps: FormatOps) { nextNonCommentSameLine(tokens(formatToken, 2)).right.is[T.Comment] val splits = baseSplits.map { s => if (willBreak || s.isNL) s.withIndent(indent) - else s.andThenPolicyOpt(delayedBreakPolicy) + else s.andPolicyOpt(delayedBreakPolicy) } if (prevSelect.isEmpty) splits @@ -1293,9 +1290,12 @@ class Router(formatOps: FormatOps) { // the newline is too cheap even it doesn't actually prevent other newlines. val penalizeNewlinesInApply = penalizeAllNewlines(expire, 2) val noSplitPolicy = - SingleLineBlock(expire, exclude, penaliseNewlinesInsideTokens = true) - .andThen(penalizeNewlinesInApply) - val newlinePolicy = breakOnEveryDot.andThen(penalizeNewlinesInApply) + SingleLineBlock( + expire, + exclude, + penaliseNewlinesInsideTokens = true + ) & penalizeNewlinesInApply + val newlinePolicy = breakOnEveryDot & penalizeNewlinesInApply val ignoreNoSplit = style.optIn.breakChainOnFirstMethodDot && tok.hasBreak val chainLengthPenalty = @@ -1441,7 +1441,7 @@ class Router(formatOps: FormatOps) { Split(Newline, 1) .withIndent(style.continuationIndent.callSite, close, Before) .withPolicy(penalizeNewlines) - .andThenPolicy(decideNewlinesOnlyBeforeClose(close)) + .andPolicy(decideNewlinesOnlyBeforeClose(close)) ) else { val indent: Length = @@ -1591,7 +1591,7 @@ class Router(formatOps: FormatOps) { else Some(getSingleLineInfixPolicy(close)) spaceSplitWithoutPolicy .withSingleLine(close) - .andThenPolicyOpt(singleLineInfixPolicy) + .andPolicyOpt(singleLineInfixPolicy) }, newlineSplit(10, true) ) @@ -2121,7 +2121,7 @@ class Router(formatOps: FormatOps) { case Decision(ft, s) if s.isEmpty && ft.right.is[Token.KwYield] => Seq(Split(Space, 0)) } - Right(SingleLineBlock(expire).andThen(noBreakOnYield)) + Right(SingleLineBlock(expire) & noBreakOnYield) // we force newlines in try/catch/finally case _: Term.Try | _: Term.TryWithHandler => Left(Split.ignored) // don't tuck curried apply 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 20bead193f..9fec57449a 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 @@ -98,8 +98,8 @@ case class Split( newPolicy: => Policy, ignore: Boolean = false )(implicit line: sourcecode.Line): Split = { - if (policy != NoPolicy) - throw new UnsupportedOperationException("Can't have two policies yet.") + if (!policy.isEmpty) + throw new UnsupportedOperationException("Use orPolicy or andPolicy") if (isIgnored || ignore) this else copy(policy = newPolicy) } @@ -147,22 +147,26 @@ case class Split( )(implicit line: sourcecode.Line): Split = if (isIgnored) this else newPolicy.fold(this)(withPolicy(_)) - def orElsePolicy(newPolicy: Policy): Split = + def orPolicy(newPolicy: Policy): Split = if (isIgnored || newPolicy.isEmpty) this - else if (policy.isEmpty) copy(policy = newPolicy) - else copy(policy = policy.orElse(newPolicy)) + else copy(policy = policy | newPolicy)(line = line) - def andThenPolicy(newPolicy: Policy): Split = + def andPolicy(newPolicy: Policy): Split = if (isIgnored || newPolicy.isEmpty) this - else if (policy.isEmpty) copy(policy = newPolicy) - else copy(policy = policy.andThen(newPolicy)) + else copy(policy = policy & newPolicy)(line = line) - def andThenPolicy(newPolicy: => Policy, ignore: Boolean): Split = - if (ignore) this else andThenPolicy(newPolicy) + def andPolicy(newPolicy: => Policy, ignore: Boolean): Split = + if (ignore) this else andPolicy(newPolicy) - def andThenPolicyOpt(newPolicy: => Option[Policy]): Split = - if (isIgnored) this - else newPolicy.fold(this)(andThenPolicy) + def andPolicyOpt(newPolicy: => Option[Policy]): Split = + if (isIgnored) this else newPolicy.fold(this)(andPolicy) + + def andFirstPolicy(newPolicy: Policy): Split = + if (isIgnored || newPolicy.isEmpty) this + else copy(policy = newPolicy & policy)(line = line) + + def andFirstPolicyOpt(newPolicy: => Option[Policy]): Split = + if (isIgnored) this else newPolicy.fold(this)(andFirstPolicy) def withPenalty(penalty: Int): Split = if (isIgnored || penalty <= 0) this else copy(cost = cost + penalty) From 4ae6a7b355ce7757b381805b5d375cad39df2b32 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:25:13 -0700 Subject: [PATCH 4/8] FormatOps: fix policy expire in vertical multiline --- .../org/scalafmt/internal/FormatOps.scala | 127 ++++++++---------- .../scala/org/scalafmt/internal/Router.scala | 7 +- 2 files changed, 56 insertions(+), 78 deletions(-) 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 68a6dde004..26b19aeed8 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 @@ -371,56 +371,50 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { }.getOrElse(tree.tokens.last) @inline - def OneArgOneLineSplit(tok: FormatToken)(implicit - line: sourcecode.Line, - style: ScalafmtConfig - ): Policy = - if (style.poorMansTrailingCommasInConfigStyle) - splitOneArgPerLineBeforeComma(tok) - else - splitOneArgPerLineAfterComma(tok) - - def splitOneArgPerLineBeforeComma(tok: FormatToken)(implicit + def splitOneArgOneLine(close: Token, owner: Tree)(implicit line: sourcecode.Line, style: ScalafmtConfig ): Policy = { - val owner = tok.meta.leftOwner - // TODO(olafur) clear queue between arguments, they are independent. - 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.modExt.mod ne NoSplit) x else x.withMod(Newline)) - - case Decision(t @ FormatToken(_: T.Comma, right, _), splits) - if owner == t.meta.leftOwner && - !right.is[T.LeftBrace] && - // If comment is bound to comma, see unit/Comment. - (!right.is[T.Comment] || t.hasBreak) => - val isNewline = right.is[T.Comment] - splits.filter(_.isNL == isNewline) - } + val pf = + if (style.poorMansTrailingCommasInConfigStyle) + splitOneArgPerLineBeforeComma(owner) + else + splitOneArgPerLineAfterComma(owner) + Policy(close)(pf) } - def splitOneArgPerLineAfterComma(tok: FormatToken)(implicit - line: sourcecode.Line, - style: ScalafmtConfig - ): Policy = { - val owner = tok.meta.leftOwner + def splitOneArgPerLineBeforeComma( + owner: Tree + )(implicit style: ScalafmtConfig): Policy.Pf = { // TODO(olafur) clear queue between arguments, they are independent. - Policy(matching(tok.left)) { - // Newline on every comma. - case Decision(t @ FormatToken(_: T.Comma, right, _), splits) - if owner == t.meta.leftOwner && - // TODO(olafur) what the right { decides to be single line? - // If comment is bound to comma, see unit/Comment. - (!right.is[T.Comment] || t.hasBreak) => - if (!right.is[T.LeftBrace]) - splits.filter(_.isNL) - else if (!style.activeForEdition_2020_03) - splits - else - SplitTag.OneArgPerLine.activateOnly(splits) - } + case Decision(t @ FormatToken(_, _: T.Comma, _), splits) + if owner == t.meta.rightOwner && !next(t).right.is[T.Comment] => + 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 && + !right.is[T.LeftBrace] && + // If comment is bound to comma, see unit/Comment. + (!right.is[T.Comment] || t.hasBreak) => + val isNewline = right.is[T.Comment] + splits.filter(_.isNL == isNewline) + } + + def splitOneArgPerLineAfterComma( + owner: Tree + )(implicit style: ScalafmtConfig): Policy.Pf = { + // Newline on every comma. + case Decision(t @ FormatToken(_: T.Comma, right, _), splits) + if owner == t.meta.leftOwner && + // TODO(olafur) what the right { decides to be single line? + // If comment is bound to comma, see unit/Comment. + (!right.is[T.Comment] || t.hasBreak) => + if (!right.is[T.LeftBrace]) + splits.filter(_.isNL) + else if (!style.activeForEdition_2020_03) + splits + else + SplitTag.OneArgPerLine.activateOnly(splits) } def UnindentAtExclude( @@ -1152,33 +1146,24 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { val isBracket = open.is[T.LeftBracket] @tailrec - def loop(token: Token): Option[FormatToken] = { - tokens(matching(token)) match { - case FormatToken(RightParenOrBracket(), l @ T.LeftParen(), _) => - loop(l) - case f @ FormatToken(RightParenOrBracket(), right, _) => - lazy val isCtorModifier = - f.meta.rightOwner.parent.exists(_.is[meta.Ctor]) - right match { - // modifier for constructor if class definition has type parameters: [class A[T, K, C] private (a: Int)] - case Modifier() if isCtorModifier => - // This case only applies to classes - next(f).right match { - case x @ (_: T.LeftParen | _: T.LeftBracket) => - loop(x) - case _ => - Some(f) - } - case _ => - Some(f) + def loop(token: Token): FormatToken = { + val f = tokens(token) + f.right match { + case x: T.LeftParen => loop(matching(x)) + // modifier for constructor if class definition has type parameters: [class A[T, K, C] private (a: Int)] + case Modifier() if f.meta.rightOwner.parent.exists(_.is[Ctor]) => + // This case only applies to classes + next(f).right match { + case x @ LeftParenOrBracket() => loop(matching(x)) + case _ => f } - case _ => None + case _ => f } } // find the last param on the defn so that we can apply our `policy` // till the end. - val lastParenFt = loop(open).get + val lastParenFt = loop(close) val lastParen = lastParenFt.left val mixedParams = { @@ -1195,15 +1180,9 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { // create two (2) OneArgOneLineSplit when dealing with classes. One // deals with the type params and the other with the value params. val oneLinePerArg = { - val base = OneArgOneLineSplit(ft) - if (mixedParams) { - val afterTypes = tokens(matching(open)) - // Try to find the first paren. If found, then we are dealing with - // a class with type AND value params. Otherwise it is a class with - // just type params. - findFirst(afterTypes, lastParen)(t => t.left.is[T.LeftParen]) - .fold(base)(t => base | OneArgOneLineSplit(t)) - } else base + val base = splitOneArgOneLine(lastParen, ft.meta.leftOwner) + if (!mixedParams || (close eq lastParen)) base + else base | splitOneArgOneLine(lastParen, lastParenFt.meta.leftOwner) } // DESNOTE(2017-03-28, pjrt) Classes and defs aren't the same. 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 ab5ee75842..c359e76e2d 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 @@ -135,7 +135,7 @@ class Router(formatOps: FormatOps) { val newlinePolicy = style.importSelectors match { case ImportSelectors.noBinPack => - newlineBeforeClosingCurly & OneArgOneLineSplit(formatToken) + newlineBeforeClosingCurly & splitOneArgOneLine(close, leftOwner) case ImportSelectors.binPack => newlineBeforeClosingCurly case ImportSelectors.singleLine => @@ -804,8 +804,7 @@ class Router(formatOps: FormatOps) { val preferNoSplit = singleArgument && style.newlines.sourceIs(Newlines.keep) && tok.noBreak - val oneArgOneLine = - newlinePolicy & OneArgOneLineSplit(formatToken) + val oneArgOneLine = newlinePolicy & splitOneArgOneLine(close, leftOwner) val extraOneArgPerLineIndent = if (multipleArgs && style.poorMansTrailingCommasInConfigStyle) Indent(Num(2), right, After) @@ -974,7 +973,7 @@ class Router(formatOps: FormatOps) { val nlPolicy = if (onlyConfigStyle) { if (styleMap.forcedBinPack(leftOwner)) newlineBeforeClose - else OneArgOneLineSplit(formatToken) | newlineBeforeClose + else splitOneArgOneLine(close, leftOwner) | newlineBeforeClose } else if ( style.newlines.sourceIgnored && style.danglingParentheses.callSite From 65fe5470e7f0f2a20dd20a12bb9db0270cb1bdb1 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Fri, 26 Jun 2020 23:24:02 -0700 Subject: [PATCH 5/8] FormatOps: pass expiration to delayedBreakPolicy --- .../main/scala/org/scalafmt/internal/FormatOps.scala | 10 +++++++--- .../src/main/scala/org/scalafmt/internal/Router.scala | 5 ++--- 2 files changed, 9 insertions(+), 6 deletions(-) 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 26b19aeed8..cfd6801dac 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 @@ -1062,7 +1062,8 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { } def delayedBreakPolicy( - leftCheck: Option[Token => Boolean] + tok: Token, + leftCheck: Option[Token => Boolean] = None )(onBreakPolicy: Policy)(implicit line: sourcecode.Line): Policy = { object OnBreakDecision { def unapply(d: Decision): Option[Seq[Split]] = @@ -1080,8 +1081,11 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { if (replaced) Some(splits) else None } } - if (onBreakPolicy.isEmpty) onBreakPolicy - else onBreakPolicy.copy(f = { case OnBreakDecision(d) => d }) + if (onBreakPolicy.isEmpty) Policy.NoPolicy + else { + val f: Policy.Pf = { case OnBreakDecision(d) => d } + onBreakPolicy.copy(f = f, expire = tok.end) + } } def decideNewlinesOnlyBeforeClose( 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 c359e76e2d..4b64dd1ab5 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 @@ -575,8 +575,7 @@ class Router(formatOps: FormatOps) { s.filter(x => x.isNL && (x.activeTag ne SplitTag.OnelineWithChain)) } val policyEnd = defnBeforeTemplate(leftOwner).fold(r)(_.tokens.last) - val policy = delayedBreakPolicy(None)(forceNewlineBeforeExtends) - .copy(expire = policyEnd.end) + val policy = delayedBreakPolicy(policyEnd)(forceNewlineBeforeExtends) Seq(Split(Space, 0).withPolicy(policy)) // DefDef case tok @ FormatToken(T.KwDef(), name @ T.Ident(_), _) => @@ -618,7 +617,7 @@ class Router(formatOps: FormatOps) { if (lambdaIsABlock) None else newlinePolicy.map( - delayedBreakPolicy(lambdaLeft.map(open => _.end < open.end)) + delayedBreakPolicy(close, lambdaLeft.map(x => _.end < x.end)) ) } From 35cf9df87bfec13badeec749bd50fcf6185939b3 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Fri, 26 Jun 2020 23:24:38 -0700 Subject: [PATCH 6/8] Policy: add clauses and OrElse/AndThen types --- .../org/scalafmt/internal/FormatOps.scala | 8 +- .../scala/org/scalafmt/internal/Policy.scala | 139 +++++++++++------- .../org/scalafmt/internal/PolicySummary.scala | 4 +- .../scala/org/scalafmt/util/TokenOps.scala | 26 ++-- .../VerticalMultilineDefnSite.stat | 6 +- 5 files changed, 107 insertions(+), 76 deletions(-) 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 cfd6801dac..6594b21b81 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 @@ -1084,7 +1084,13 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { if (onBreakPolicy.isEmpty) Policy.NoPolicy else { val f: Policy.Pf = { case OnBreakDecision(d) => d } - onBreakPolicy.copy(f = f, expire = tok.end) + new Policy.Clause(f, tok.end) { + override def filter(pred: Policy.Clause => Boolean): Policy = { + val filteredOnBreak = onBreakPolicy.filter(pred) + if (filteredOnBreak.isEmpty) Policy.NoPolicy + else delayedBreakPolicy(tok, leftCheck)(filteredOnBreak) + } + } } } 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 3d7637cd81..af2a25ce9f 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 @@ -6,77 +6,106 @@ import scala.meta.tokens.Token * The decision made by [[Router]]. * * Used by [[Policy]] to enforce non-local formatting. - * - * @param f is applied to every decision until expire - * @param expire The latest token offset. */ -case class Policy( - f: Policy.Pf, - expire: Int, - noDequeue: Boolean = false -)(implicit val line: sourcecode.Line) { - - def isEmpty: Boolean = Policy.isEmpty(f) - - def orElse(other: Policy.Pf, minExpire: Int = 0): Policy = - if (Policy.isEmpty(other)) this - else - copy( - f = if (isEmpty) other else f.orElse(other), - expire = math.max(minExpire, expire) - ) +abstract class Policy { - def |(other: Policy): Policy = - orElse(other.f, other.expire) + /** applied to every decision until expire */ + def f: Policy.Pf - def |(other: Option[Policy]): Policy = - other.fold(this)(|) + def exists(pred: Policy.Clause => Boolean): Boolean + def filter(pred: Policy.Clause => Boolean): Policy + def unexpired(pos: Int): Policy = filter(_.endPos > pos) def &(other: Policy): Policy = - if (isEmpty) other else andThen(other.f, other.expire) - - /** Similar to PartialFunction.andThen, except applies second pf even if the - * first pf is not defined at argument. - */ - def andThen(otherF: Policy.Pf, minExpire: Int = 0): Policy = { - if (Policy.isEmpty(otherF)) this - else if (isEmpty) copy(f = otherF) - else { - // TODO(olafur) optimize? - val newPf: Policy.Pf = { - case x => - otherF.applyOrElse( - f.andThen(x.withSplits _).applyOrElse(x, identity[Decision]), - (x: Decision) => x.splits - ) - } - copy(f = newPf, expire = math.max(minExpire, expire)) - } - } + if (other.isEmpty) this else new Policy.AndThen(this, other) + def |(other: Policy): Policy = + if (other.isEmpty) this else new Policy.OrElse(this, other) + + @inline + final def unexpiredOpt(pos: Int): Option[Policy] = + Some(unexpired(pos)).filter(_.nonEmpty) + + @inline + final def &(other: Option[Policy]): Policy = other.fold(this)(&) + @inline + final def |(other: Option[Policy]): Policy = other.fold(this)(|) + + @inline + final def isEmpty: Boolean = this eq Policy.NoPolicy + @inline + final def nonEmpty: Boolean = this ne Policy.NoPolicy - override def toString = s"P:${line.value}(D=$noDequeue)E:$expire" } object Policy { type Pf = PartialFunction[Decision, Seq[Split]] - val emptyPf: Pf = PartialFunction.empty - - val NoPolicy = new Policy(emptyPf, Integer.MAX_VALUE)(sourcecode.Line(0)) { + object NoPolicy extends Policy { override def toString: String = "NoPolicy" + override def f: Pf = PartialFunction.empty + override def |(other: Policy): Policy = other + override def &(other: Policy): Policy = other + + override def unexpired(pos: Int): Policy = this + override def filter(pred: Clause => Boolean): Policy = this + override def exists(pred: Clause => Boolean): Boolean = false } - def empty(token: Token)(implicit - line: sourcecode.Line - ): Policy = Policy(token)(emptyPf) + def apply(token: Token)(f: Pf)(implicit line: sourcecode.Line): Policy = + apply(token.end)(f) + + def apply( + endPos: Int, + noDequeue: Boolean = false + )(f: Pf)(implicit line: sourcecode.Line): Policy = + new Clause(f, endPos, noDequeue) + + class Clause( + val f: Policy.Pf, + val endPos: Int, + val noDequeue: Boolean = false + )(implicit val line: sourcecode.Line) + extends Policy { + override def toString = { + val noDeqPrefix = if (noDequeue) "!" else "" + s"P:${line.value}<$endPos${noDeqPrefix}d" + } - def map(func: Token => Pf)(token: Token)(implicit - line: sourcecode.Line - ): Policy = Policy(token)(func(token)) + override def filter(pred: Clause => Boolean): Policy = + if (pred(this)) this else NoPolicy - def apply(token: Token)(f: Pf)(implicit line: sourcecode.Line): Policy = - new Policy(f, token.end) + override def exists(pred: Clause => Boolean): Boolean = pred(this) + } + + private class OrElse(p1: Policy, p2: Policy) extends Policy { + override lazy val f: Pf = p1.f.orElse(p2.f) + + override def filter(pred: Clause => Boolean): Policy = + p1.filter(pred) | p2.filter(pred) + + override def exists(pred: Clause => Boolean): Boolean = + p1.exists(pred) || p2.exists(pred) + + override def toString: String = s"($p1 | $p2)" + } + + private class AndThen(p1: Policy, p2: Policy) extends Policy { + override lazy val f: Pf = { + case x => + p2.f.applyOrElse( + p1.f.andThen(x.withSplits _).applyOrElse(x, identity[Decision]), + (y: Decision) => y.splits + ) + } + + override def filter(pred: Clause => Boolean): Policy = + p1.filter(pred) & p2.filter(pred) + + override def exists(pred: Clause => Boolean): Boolean = + p1.exists(pred) || p2.exists(pred) + + override def toString: String = s"($p1 & $p2)" + } - def isEmpty(pf: Pf): Boolean = pf == emptyPf } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala index e9bfeb1b4f..01fe376eee 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala @@ -6,11 +6,11 @@ import org.scalafmt.util.LoggerOps class PolicySummary(val policies: Vector[Policy]) { import LoggerOps._ - @inline def noDequeue = policies.exists(_.noDequeue) + @inline def noDequeue = policies.exists(_.exists(_.noDequeue)) def combine(other: Policy, position: Int): PolicySummary = { // TODO(olafur) filter policies by expiration date - val activePolicies = policies.filter(_.expire > position) + val activePolicies = policies.flatMap(_.unexpiredOpt(position)) val newPolicies = if (other == NoPolicy) activePolicies else other +: activePolicies diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala index 852166e401..f13769a20c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala @@ -113,21 +113,17 @@ object TokenOps { disallowSingleLineComments: Boolean = true, penaliseNewlinesInsideTokens: Boolean = false )(implicit line: sourcecode.Line): Policy = { - Policy( - { - case d @ Decision(tok, _) - if !tok.right.is[EOF] && tok.right.end <= expire.end && - exclude.forall(!_.contains(tok.left.start)) && - (disallowSingleLineComments || !isSingleLineComment(tok.left)) => - if (penaliseNewlinesInsideTokens && tok.leftHasNewline) { - Seq.empty - } else { - d.noNewlines - } - }, - expire.end, - noDequeue = true - ) + Policy(expire.end, true) { + case d @ Decision(tok, _) + if !tok.right.is[EOF] && tok.right.end <= expire.end && + exclude.forall(!_.contains(tok.left.start)) && + (disallowSingleLineComments || !isSingleLineComment(tok.left)) => + if (penaliseNewlinesInsideTokens && tok.leftHasNewline) { + Seq.empty + } else { + d.noNewlines + } + } } @inline diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat b/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat index 4b1a0895b4..e7221eb240 100644 --- a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat +++ b/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat @@ -53,7 +53,7 @@ object a { object a { final class Dummy[F[+_, +_]: BIOMonad: BIOPrimitives: Clock2]( log: LogBIO[F] - ) extends DIResource.LiftF(F - .mkRef(Map.empty[Key, Item]) - .map(new Dummy.Impl(_))) + ) extends DIResource.LiftF( + F.mkRef(Map.empty[Key, Item]).map(new Dummy.Impl(_)) + ) } From 71354d94c89d1bde2d12d0913e88a90d02d861a9 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Sat, 27 Jun 2020 10:36:01 -0700 Subject: [PATCH 7/8] PolicyOps: move PenalizeAllNewlines --- .../org/scalafmt/internal/FormatOps.scala | 20 ---------- .../scala/org/scalafmt/internal/Router.scala | 1 + .../scala/org/scalafmt/util/PolicyOps.scala | 38 +++++++++++++++++++ 3 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala 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 6594b21b81..bc65b28e08 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 @@ -426,26 +426,6 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { s.map(_.withIndent(indent, close, ExpiresOn.After)) } - def penalizeAllNewlines( - expire: Token, - penalty: Int, - penalizeLambdas: Boolean = true, - ignore: FormatToken => Boolean = _ => false, - penaliseNewlinesInsideTokens: Boolean = false - )(implicit line: sourcecode.Line): Policy = - Policy(expire) { - case Decision(tok, s) - if tok.right.end < expire.end && - (penalizeLambdas || !tok.left.is[T.RightArrow]) && !ignore(tok) => - s.map { - case split - if split.isNL || - (penaliseNewlinesInsideTokens && tok.leftHasNewline) => - split.withPenalty(penalty) - case x => x - } - } - def penalizeNewlineByNesting(from: Token, to: Token)(implicit line: sourcecode.Line ): Policy = { 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 4b64dd1ab5..16c69183b7 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 @@ -61,6 +61,7 @@ class Router(formatOps: FormatOps) { import Constants._ import LoggerOps._ + import PolicyOps._ import TokenOps._ import TreeOps._ import formatOps._ 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 new file mode 100644 index 0000000000..c3c967e7a2 --- /dev/null +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala @@ -0,0 +1,38 @@ +package org.scalafmt.util + +import scala.meta.tokens.{Token => T} + +import org.scalafmt.internal.Decision +import org.scalafmt.internal.FormatToken +import org.scalafmt.internal.Policy + +object PolicyOps { + + class PenalizeAllNewlines private[PolicyOps] (f: Policy.Pf, endPos: Int)( + implicit line: sourcecode.Line + ) extends Policy.Clause(f, endPos) + + def penalizeAllNewlines( + expire: T, + penalty: Int, + penalizeLambdas: Boolean = true, + ignore: FormatToken => Boolean = _ => false, + penaliseNewlinesInsideTokens: Boolean = false + )(implicit line: sourcecode.Line): PenalizeAllNewlines = { + val endPos = expire.end + val f: Policy.Pf = { + case Decision(tok, s) + if tok.right.end < endPos && + (penalizeLambdas || !tok.left.is[T.RightArrow]) && !ignore(tok) => + s.map { + case split + if split.isNL || + (penaliseNewlinesInsideTokens && tok.leftHasNewline) => + split.withPenalty(penalty) + case x => x + } + } + new PenalizeAllNewlines(f, endPos) + } + +} From 258466ec94df560ce8f3052316bd937ceb5cbfcb Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Sat, 27 Jun 2020 10:34:44 -0700 Subject: [PATCH 8/8] PolicyOps: move SingleLineBlock --- .../org/scalafmt/internal/FormatOps.scala | 1 + .../scala/org/scalafmt/internal/Split.scala | 2 + .../scala/org/scalafmt/util/PolicyOps.scala | 37 +++++++++++++++++++ .../scala/org/scalafmt/util/TokenOps.scala | 24 ------------ 4 files changed, 40 insertions(+), 24 deletions(-) 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 bc65b28e08..c2a9230d28 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 @@ -55,6 +55,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) { else baseStyle.copy(newlines = checkedNewlines) } val runner = initStyle.runner + import PolicyOps._ import TokenOps._ import TreeOps._ implicit val dialect = initStyle.runner.dialect 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 9fec57449a..52422e21c1 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 @@ -3,6 +3,7 @@ package org.scalafmt.internal import scala.meta.tokens.Token import org.scalafmt.internal.Policy.NoPolicy +import org.scalafmt.util.PolicyOps import org.scalafmt.util.TokenOps case class OptimalToken(token: Token, killOnFail: Boolean = false) { @@ -42,6 +43,7 @@ case class Split( policy: Policy = NoPolicy, optimalAt: Option[OptimalToken] = None )(implicit val line: sourcecode.Line) { + import PolicyOps._ import TokenOps._ def adapt(formatToken: FormatToken): Split = 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 c3c967e7a2..8788ca6dc9 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 @@ -35,4 +35,41 @@ object PolicyOps { new PenalizeAllNewlines(f, endPos) } + /** + * Forces allssplits up to including expire to be on a single line. + */ + class SingleLineBlock private[PolicyOps] ( + f: Policy.Pf, + endPos: Int, + exclude: Set[Range] + )(implicit line: sourcecode.Line) + extends Policy.Clause(f, endPos, true) { + override def toString: String = + super.toString + { + if (exclude.isEmpty) "" + else exclude.map(x => s"${x.start}:${x.end}").mkString("^{", ",", "}") + } + } + + def SingleLineBlock( + expire: T, + exclude: Set[Range] = Set.empty, + disallowSingleLineComments: Boolean = true, + penaliseNewlinesInsideTokens: Boolean = false + )(implicit line: sourcecode.Line): Policy = { + import TokenOps.isSingleLineComment + val endPos = expire.end + val f: Policy.Pf = { + case Decision(tok, s) + if !tok.right.is[T.EOF] && tok.right.end <= endPos && + exclude.forall(!_.contains(tok.left.start)) && + (disallowSingleLineComments || !isSingleLineComment(tok.left)) => + if (penaliseNewlinesInsideTokens && tok.leftHasNewline) + Seq.empty + else + s.filterNot(_.isNL) + } + new SingleLineBlock(f, endPos, exclude) + } + } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala index f13769a20c..f7f2966972 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala @@ -8,11 +8,9 @@ import scala.meta.tokens.Tokens import org.scalafmt.config.Newlines import org.scalafmt.config.ScalafmtConfig -import org.scalafmt.internal.Decision import org.scalafmt.internal.FormatToken import org.scalafmt.internal.Modification import org.scalafmt.internal.NewlineT -import org.scalafmt.internal.Policy import org.scalafmt.internal.Space /** @@ -104,28 +102,6 @@ object TokenOps { case _ => false } - /** - * Forces allssplits up to including expire to be on a single line. - */ - def SingleLineBlock( - expire: Token, - exclude: Set[Range] = Set.empty, - disallowSingleLineComments: Boolean = true, - penaliseNewlinesInsideTokens: Boolean = false - )(implicit line: sourcecode.Line): Policy = { - Policy(expire.end, true) { - case d @ Decision(tok, _) - if !tok.right.is[EOF] && tok.right.end <= expire.end && - exclude.forall(!_.contains(tok.left.start)) && - (disallowSingleLineComments || !isSingleLineComment(tok.left)) => - if (penaliseNewlinesInsideTokens && tok.leftHasNewline) { - Seq.empty - } else { - d.noNewlines - } - } - } - @inline def isSingleLineComment(c: String): Boolean = c.startsWith("//")