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

Router: allow space after "{" in partial functions #2108

Merged
merged 5 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,47 @@ else {
}
```

### `newlines.beforeCurlyLambdaParams`

This parameter controls whether a newline is forced between the opening curly brace
and the parameters of a lambda or partial function. Added in 2.7.0, replacing
boolean `alwaysBeforeCurlyBraceLambdaParams`.

```scala mdoc:defaults
newlines.beforeCurlyLambdaParams
```

```scala mdoc:scalafmt
newlines.beforeCurlyLambdaParams = never
---
// should keep one-line
x.map { x => s"${x._1} -> ${x._2}" }
x.map { case (c, i) => s"$c -> $i" }
// should unfold, break on { since case fits on a line but lambda doesn't
x.zipWithIndex.map { case (c, i) => s"$c -> $i" }
// should unfold, break on => since case doesn't fit on a line
x.zipWithIndex.map { case (c, i) => s"$c -> $i (long comment)" }
```

```scala mdoc:scalafmt
newlines.beforeCurlyLambdaParams = always
---
// should unfold, break on {, though fits on a line
x.map { x => s"${x._1} -> ${x._2}" }
x.map { case (c, i) => s"$c -> $i" }
```

```scala mdoc:scalafmt
newlines.beforeCurlyLambdaParams = multilineWithCaseOnly
---
// should keep one-line
x.map { x => s"${x._1} -> ${x._2}" }
x.map { case (c, i) => s"$c -> $i" }
// should unfold, break on { as lambda doesn't fit on a line
x.zipWithIndex.map { case (c, i) => s"$c -> $i" }
x.zipWithIndex.map { case (c, i) => s"$c -> $i (long comment)" }
```

### `newlines.afterCurlyLambda`

This parameter controls handling of newlines after the arrow following the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@ import metaconfig.generic.Surface
* @param sometimesBeforeColonInMethodReturnType If true, scalafmt
* may choose to put a newline
* before colon : at defs.
* @param alwaysBeforeCurlyBraceLambdaParams
* If true, puts a newline after the open brace
* and the parameters list of an anonymous function.
* For example
* @param beforeCurlyLambdaParams
* - if Never, tries to use a space between the opening curly brace and the
* list of parameters of anonymous functions, and some partial functions
* (those with a single case clause and no conditions)
* - if MultilineWithCaseOnly, forces a newline in partial functions (see above)
* which can't be formatted on a single line
* - if Always, forces a newline in lambda and partial functions.
* For example:
* {{{
* something.map {
* n =>
* consume(n)
* }
* }}}
* @param afterCurlyLambda
* If `never` (default), it will remove any extra lines below curly lambdas
* {{{
Expand Down Expand Up @@ -145,15 +151,22 @@ case class Newlines(
neverBeforeJsNative: Boolean = false,
sometimesBeforeColonInMethodReturnType: Boolean = true,
penalizeSingleSelectMultiArgList: Boolean = true,
alwaysBeforeCurlyBraceLambdaParams: Boolean = false,
@annotation.DeprecatedName(
"alwaysBeforeCurlyBraceLambdaParams",
"Use newlines.beforeCurlyLambdaParams instead",
"2.7.0"
)
private val alwaysBeforeCurlyBraceLambdaParams: Boolean = false,
beforeCurlyLambdaParams: BeforeCurlyLambdaParams =
BeforeCurlyLambdaParams.never,
topLevelStatementsMinBreaks: Int = 1,
topLevelStatements: Seq[BeforeAfter] = Seq.empty,
@annotation.DeprecatedName(
"alwaysBeforeTopLevelStatements",
"Use newlines.topLevelStatements instead",
"2.5.0"
)
alwaysBeforeTopLevelStatements: Boolean = false,
private val alwaysBeforeTopLevelStatements: Boolean = false,
afterCurlyLambda: AfterCurlyLambdaParams = AfterCurlyLambdaParams.never,
implicitParamListModifierForce: Seq[BeforeAfter] = Seq.empty,
implicitParamListModifierPrefer: Option[BeforeAfter] = None,
Expand Down Expand Up @@ -235,6 +248,13 @@ case class Newlines(
avoidForSimpleOverflow.contains(AvoidForSimpleOverflow.punct)
lazy val avoidForSimpleOverflowTooLong: Boolean =
avoidForSimpleOverflow.contains(AvoidForSimpleOverflow.tooLong)

lazy val neverBeforeCurlyLambdaParams = !alwaysBeforeCurlyBraceLambdaParams &&
(beforeCurlyLambdaParams eq BeforeCurlyLambdaParams.never)

lazy val alwaysBeforeCurlyLambdaParams = alwaysBeforeCurlyBraceLambdaParams ||
(beforeCurlyLambdaParams eq BeforeCurlyLambdaParams.always)

}

object Newlines {
Expand Down Expand Up @@ -294,4 +314,20 @@ object Newlines {
ReaderUtil.oneOf[AfterCurlyLambdaParams](preserve, always, never, squash)
}

sealed abstract class BeforeCurlyLambdaParams
object BeforeCurlyLambdaParams {
case object always extends BeforeCurlyLambdaParams
case object never extends BeforeCurlyLambdaParams
case object multilineWithCaseOnly extends BeforeCurlyLambdaParams
implicit val codec: ConfCodec[BeforeCurlyLambdaParams] =
ReaderUtil.oneOfCustom[BeforeCurlyLambdaParams](
never,
always,
multilineWithCaseOnly
) {
case Conf.Bool(true) => Configured.Ok(always)
case Conf.Bool(false) => Configured.Ok(never)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,30 @@ import metaconfig._
import metaconfig.Configured._

object ReaderUtil {
// Poor mans coproduct reader
def oneOf[T: ClassTag](options: sourcecode.Text[T]*): ConfCodec[T] =
oneOfImpl((lowerCase _).compose(lowerCaseNoBackticks), options)

def oneOfIgnoreBackticks[T: ClassTag](
options: sourcecode.Text[T]*
): ConfCodec[T] =
oneOfImpl(lowerCaseNoBackticks, options)

private def lowerCase(s: String): String = s.toLowerCase
private def lowerCaseNoBackticks(s: String): String =
s.toLowerCase().replace("`", "")

private def oneOfImpl[T: ClassTag](
sanitize: String => String,
options: Seq[sourcecode.Text[T]]
): ConfCodec[T] = {
val m = options.map(x => sanitize(x.source) -> x.value).toMap
val decoder = ConfDecoder.instance[T] {
// Poor mans coproduct reader
def oneOf[T: ClassTag](options: sourcecode.Text[T]*): ConfCodec[T] = {
oneOfCustom(options: _*)(PartialFunction.empty)
}

def oneOfCustom[T: ClassTag](
options: sourcecode.Text[T]*
)(f: PartialFunction[Conf, Configured[T]]): ConfCodec[T] = {
val m = options.map(x => lowerCaseNoBackticks(x.source) -> x.value).toMap
val decoder = ConfDecoder.instance[T](f.orElse {
case Conf.Str(x) =>
m.get(sanitize(x)) match {
m.get(lowerCaseNoBackticks(x)) match {
case Some(y) =>
Ok(y)
case None =>
val available = m.keys.mkString(", ")
val msg = s"Unknown input '$x'. Expected one of: $available"
ConfError.message(msg).notOk
}
}
})
val encoder = ConfEncoder.instance[T] { value =>
options
.collectFirst {
Expand All @@ -44,4 +39,5 @@ object ReaderUtil {
}
ConfCodec.EncoderDecoderToCodec(encoder, decoder)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ case class ScalafmtConfig(

// Edition 2019-11
val activeForEdition_2019_11: Boolean = activeFor(Edition(2019, 11))
val newlinesBeforeSingleArgParenLambdaParams: Boolean =
newlines.alwaysBeforeCurlyBraceLambdaParams || !activeForEdition_2019_11
val newlinesBeforeSingleArgParenLambdaParams =
newlines.alwaysBeforeCurlyLambdaParams || !activeForEdition_2019_11
val newlinesBetweenCurlyAndCatchFinally: Boolean =
newlines.alwaysBeforeElseAfterCurlyIf && activeForEdition_2019_11

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ case class SortSettings(
object SortSettings {

implicit val SortSettingsModKeyReader: ConfCodec[ModKey] =
ReaderUtil.oneOfIgnoreBackticks[ModKey](
ReaderUtil.oneOf[ModKey](
`implicit`,
`final`,
`sealed`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,22 +190,35 @@ class Router(formatOps: FormatOps) {
else
NewlineT(tok.hasBlankLine || blankLineBeforeDocstring(tok))

// lambdaNLOnly: None for single line only
val (lambdaExpire, lambdaArrow, lambdaIndent, lambdaNLOnly) =
startsStatement(right) match {
case Some(owner: Term.Function) =>
val arrow = getFuncArrow(lastLambda(owner))
val expire = arrow.getOrElse(tokens(owner.tokens.last))
val nlOnly =
style.newlines.alwaysBeforeCurlyBraceLambdaParams
val nlOnly = Some(style.newlines.alwaysBeforeCurlyLambdaParams)
(expire, arrow.map(_.left), 0, nlOnly)
case Some(t: Case) if t.cond.isEmpty && (leftOwner match {
case Term.PartialFunction(List(`t`)) => true
case _ => false
}) =>
val arrow = getCaseArrow(t)
val nlOnly =
if (style.newlines.alwaysBeforeCurlyLambdaParams) Some(true)
else if (
style.newlines.beforeCurlyLambdaParams eq
Newlines.BeforeCurlyLambdaParams.multilineWithCaseOnly
) None
else Some(false)
(arrow, Some(arrow.left), 0, nlOnly)
case _ =>
selfAnnotation match {
case Some(anno) =>
val arrow = leftOwner.tokens.find(_.is[T.RightArrow])
val expire = arrow.getOrElse(anno.last)
(tokens(expire), arrow, 2, isSelfAnnotationNL)
(tokens(expire), arrow, 2, Some(isSelfAnnotationNL))
case _ =>
(null, None, 0, true)
(null, None, 0, None)
}
}
val lambdaPolicy =
Expand Down Expand Up @@ -254,7 +267,7 @@ class Router(formatOps: FormatOps) {
getSingleLineDecisionPre2019NovOpt

def getSingleLineLambdaDecisionOpt = {
val ok = !lambdaNLOnly &&
val ok = !lambdaNLOnly.contains(true) &&
getSpaceAndNewlineAfterCurlyLambda(newlines)._1
if (ok) Some(getSingleLineDecisionFor2019Nov) else None
}
Expand Down Expand Up @@ -303,7 +316,7 @@ class Router(formatOps: FormatOps) {
val splits = Seq(
singleLineSplit,
Split(Space, 0)
.notIf(lambdaNLOnly || lambdaPolicy == null)
.onlyIf(lambdaNLOnly.contains(false) && lambdaPolicy != null)
.notIf(style.newlines.sourceIs(Newlines.keep) && newlines != 0)
.withOptimalTokenOpt(lambdaArrow)
.withIndent(lambdaIndent, close, Before)
Expand Down
15 changes: 6 additions & 9 deletions scalafmt-tests/src/test/resources/default/Apply.stat
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ List(Split(Space, 0).withPolicy(SingleLineBlock(close)),
>>>
List(Split(Space, 0).withPolicy(SingleLineBlock(close)),
Split(nl, 1)
.withPolicy({
case Decision(t @ FormatToken(_, `close`, _), s) =>
Decision(t, List(Split(Newline, 0)))
.withPolicy({ case Decision(t @ FormatToken(_, `close`, _), s) =>
Decision(t, List(Split(Newline, 0)))
})
.withIndent(2, close, Right))
<<< Massive 2
Expand All @@ -31,9 +30,8 @@ List(Split(Space,
ignoreIf = blockSize > style.maxColumn),
Split(nl,
1,
policy = {
case Decision(t @ FormatToken(_, `close`, _), s) =>
Decision(t, List(Split(Newline, 0)))
policy = { case Decision(t @ FormatToken(_, `close`, _), s) =>
Decision(t, List(Split(Newline, 0)))
}))
<<< Massive (good API) 3
Split(Space, 0).withPolicy {
Expand Down Expand Up @@ -138,9 +136,8 @@ Seq(
// Either everything fits in one line or break on =>
Split(Space, 0).withPolicy(SingleLineBlock(lastToken)),
Split(Space, 1)
.withPolicy(Policy({
case Decision(t @ FormatToken(`arrow`, _, _), s) =>
Decision(t, s.filter(_.modification.isNewline))
.withPolicy(Policy({ case Decision(t @ FormatToken(`arrow`, _, _), s) =>
Decision(t, s.filter(_.modification.isNewline))
},
expire = lastToken.end))
.withIndent(2, lastToken, Left) // case body indented by 2.
Expand Down
24 changes: 11 additions & 13 deletions scalafmt-tests/src/test/resources/default/ApplyInfix.stat
Original file line number Diff line number Diff line change
Expand Up @@ -239,17 +239,15 @@ if (other == null) {
>>>
{
{
phrase(a ^^ {
case p1 =>
Rowset(
p1.toSeq map {
fromXML[eveapi.xml.char.ContactList.Row](_,
scalaxb.ElemName(node))
},
ListMap(
List(
(node \ "@name").headOption.map(x => DataRecord())
).flatten: _*))
phrase(a ^^ { case p1 =>
Rowset(
p1.toSeq map {
fromXML[eveapi.xml.char.ContactList.Row](_, scalaxb.ElemName(node))
},
ListMap(
List(
(node \ "@name").headOption.map(x => DataRecord())
).flatten: _*))
})
}
}
Expand All @@ -274,8 +272,8 @@ object ExternForwarder {
case DefDef()
if isExternModule(from.symbol)
&& params.length == args.length
&& params.zip(args).forall {
case _ => false
&& params.zip(args).forall { case _ =>
false
} =>
Some(sel.symbol)
case _ =>
Expand Down
4 changes: 2 additions & 2 deletions scalafmt-tests/src/test/resources/default/Case.stat
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ x match {
{
def testZipPartitions4(rdd: RDD[Int]): Unit = {
rdd
.zipPartitions(rdd, rdd, rdd) {
case (it1, it2, it3, it4) => return; it1
.zipPartitions(rdd, rdd, rdd) { case (it1, it2, it3, it4) =>
return; it1
}
.count()
}
Expand Down
17 changes: 8 additions & 9 deletions scalafmt-tests/src/test/resources/default/Idempotency.stat
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ val bindingFuture = Http().bindAndHandleSync({
{
{
val bindingFuture = Http().bindAndHandleSync(
{
case HttpRequest(_, _, headers, _, _) ⇒
val upgrade = headers.collectFirst {
case u: UpgradeToWebSocket ⇒ u
}.get
upgrade.handleMessages(
Flow.fromSinkAndSource(Sink.ignore,
Source.fromPublisher(source)),
None)
{ case HttpRequest(_, _, headers, _, _) ⇒
val upgrade = headers.collectFirst {
case u: UpgradeToWebSocket ⇒ u
}.get
upgrade.handleMessages(
Flow.fromSinkAndSource(Sink.ignore,
Source.fromPublisher(source)),
None)
},
interface = "localhost",
port = 0)
Expand Down
7 changes: 3 additions & 4 deletions scalafmt-tests/src/test/resources/default/Lambda.stat
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,9 @@ def test(dataFrame: DataFrame) = {
dataFrame.rdd
.aggregate(zeroAccumulator)(
(acc, row) =>
acc.zip(row.toSeq).map {
case (x, y) =>
if (true) (1, 2)
else (x._1 + 2, x._2 + 1)
acc.zip(row.toSeq).map { case (x, y) =>
if (true) (1, 2)
else (x._1 + 2, x._2 + 1)
},
(acc1, acc2) => acc1.zip(acc2).map { case (x, y) => (x._1 + y._1, x._2 + y._2) }
)
Expand Down
Loading