Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy: preserve original policies and ranges for control #2060

Merged
merged 8 commits into from
Jul 3, 2020
225 changes: 102 additions & 123 deletions scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -371,56 +372,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(
Expand All @@ -432,26 +427,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 = {
Expand Down Expand Up @@ -741,11 +716,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),
Expand All @@ -770,18 +745,18 @@ 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)
.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)
}

Expand Down Expand Up @@ -1068,7 +1043,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]] =
Expand All @@ -1080,35 +1056,53 @@ 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
}
}
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 }
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)
}
}
}
}

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
Expand Down Expand Up @@ -1143,33 +1137,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 = {
Expand All @@ -1186,15 +1171,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.orElse(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.
Expand All @@ -1209,7 +1188,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(
Expand Down Expand Up @@ -1250,7 +1229,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 | paramGroupSplitter

val firstIndent =
if (r.is[T.RightParen]) // An empty param group
Expand Down
Loading