diff --git a/docs/configuration.md b/docs/configuration.md index 2bcbd78838..437150a0ca 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -975,6 +975,19 @@ object a { } ``` +### `align.beforeOpenParenXxxSite` + +Aligns parameter groups (not parameters within a group) if using +[`newlines.beforeOpenParenXxxSite`](#newlinesbeforeopenparenxxxsite). +Requires [`align.closeParenSite`](#aligncloseparensite). + +> Since v3.3.2. + +```scala mdoc:defaults +align.beforeOpenParenCallSite +align.beforeOpenParenDefnSite +``` + ### `align.stripMargin` See [assumeStandardLibraryStripMargin](#assumestandardlibrarystripmargin). @@ -2068,6 +2081,9 @@ Additional nuances: - if the corresponding `align.openParenXxxSite` is true, multi-line parameters will start on the same line as the opening parenthesis and align; otherwise, formatting will use a newline and an appropriate continuation indent. +- if the corresponding [`align.beforeOpenParenXxxSite`](#alignbeforeopenparenxxxsite) + is true, when the first parameter group starts without a line break, subsequent + parameter groups will be aligned to it. ```scala mdoc:defaults newlines.beforeOpenParenDefnSite diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala index cab96fca3d..cf4a308ac5 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala @@ -67,6 +67,8 @@ case class Align( private val openBracketDefnSite: Option[Boolean] = None, openParenDefnSite: Boolean = false, private[config] val openParenTupleSite: Option[Boolean] = None, + beforeOpenParenDefnSite: Boolean = false, + beforeOpenParenCallSite: Boolean = false, tokens: Seq[AlignToken] = Seq(AlignToken.caseArrow), arrowEnumeratorGenerator: Boolean = false, tokenCategory: Map[String, String] = Map(), diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala index 53e5b74c5a..e612e37768 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala @@ -415,6 +415,8 @@ object ScalafmtConfig { addIf(rewrite.insertBraces.minLines != 0 && rewrite.scala3.removeOptionalBraces == RemoveOptionalBraces.oldSyntaxToo) if (rewrite.insertBraces.minLines != 0 && rewrite.rules.contains(RedundantBraces)) addIf(rewrite.insertBraces.minLines < rewrite.redundantBraces.maxBreaks) + addIf(align.beforeOpenParenDefnSite && !align.closeParenSite) + addIf(align.beforeOpenParenCallSite && !align.closeParenSite) } // scalafmt: {} if (allErrors.isEmpty) Configured.ok(cfg) 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 f897135be4..1a1ab4bed5 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 @@ -1600,7 +1600,7 @@ class FormatOps( private def getOpenNLByTree( fun: Tree, - argsOrArgss: Either[Seq[Tree], Seq[Seq[Tree]]], + argsOrArgss: CallArgs, penalty: Int ): Seq[Policy] = { val argss = argsOrArgss match { 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 f528591b88..c59e0c6a99 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 @@ -1,7 +1,7 @@ package org.scalafmt.internal import org.scalafmt.Error.UnexpectedTree -import org.scalafmt.config.BinPack +import org.scalafmt.config.{Align, BinPack} import org.scalafmt.config.{ImportSelectors, Newlines, ScalafmtConfig, Spaces} import org.scalafmt.internal.ExpiresOn.{After, Before} import org.scalafmt.internal.Length.{Num, StateColumn} @@ -622,11 +622,17 @@ class Router(formatOps: FormatOps) { val beforeDefRhs = defRhs.flatMap(tokens.tokenJustBeforeOpt) def getSplitsBeforeOpenParen( src: Newlines.SourceHints, - indentLen: Int - ) = { + indentLen: Int, + shouldAlignBefore: Align => Boolean + )(getArgsOpt: => Option[Seq[Tree]]) = { val close = matching(open) val indent = Indent(indentLen, close, ExpiresOn.After) - src match { + val isAlignFirstParen = shouldAlignBefore(style.align) && + !prevNonComment(ft).left.is[T.RightParen] + def noSplitSplit(implicit fileLine: FileLine) = + if (isAlignFirstParen) Split(NoSplit, 0) + else Split(NoSplit, 0).withSingleLine(close) + val splits = src match { case Newlines.unfold => val slbEnd = if (defn) beforeDefRhs.fold(getLastToken(rightOwner))(_.left) @@ -657,7 +663,7 @@ class Router(formatOps: FormatOps) { Seq(Split(Newline, 0).withIndent(indent)) else Seq( - Split(NoSplit, 0).withSingleLine(close), + noSplitSplit, Split(Newline, 1).withIndent(indent) ) case _ => @@ -672,12 +678,22 @@ class Router(formatOps: FormatOps) { } } Seq( - Split(NoSplit, 0).withSingleLine(close), + noSplitSplit, Split(Newline, 1) .withIndent(indent) .withPolicyOpt(nlColonPolicy) ) } + val argsOpt = if (isAlignFirstParen) getArgsOpt else None + argsOpt.map(x => tokens.tokenAfter(x).right).fold(splits) { x => + val noSplitIndents = Seq( + Indent(StateColumn, x, ExpiresOn.Before), + Indent(-indentLen, x, ExpiresOn.Before) + ) + splits.map { s => + if (s.isNL) s else s.withIndents(noSplitIndents) + } + } } val beforeOpenParenSplits = if (!open.is[T.LeftParen]) None @@ -692,12 +708,31 @@ class Router(formatOps: FormatOps) { style.indent.extraBeforeOpenParenDefnSite + (if (ob) style.indent.getSignificant else style.indent.main) } - getSplitsBeforeOpenParen(x, indent) + getSplitsBeforeOpenParen(x, indent, _.beforeOpenParenDefnSite) { + rightOwner match { + case SplitDefnIntoParts(_, _, _, args) => Some(args.last) + case _ => None + } + } } else if (style.dialect.allowSignificantIndentation) - style.newlines.getBeforeOpenParenCallSite.map( - getSplitsBeforeOpenParen(_, style.indent.getSignificant) - ) + style.newlines.getBeforeOpenParenCallSite.map { x => + val indent = style.indent.getSignificant + @tailrec + def findLastCallArgs(tree: Tree, ca: CallArgs): CallArgs = + tree match { + case SplitCallIntoParts(_, pca) => + tree.parent match { + case Some(p) => findLastCallArgs(p, pca) + case _ => pca + } + case _ => ca + } + getSplitsBeforeOpenParen(x, indent, _.beforeOpenParenCallSite) { + Option(findLastCallArgs(rightOwner, null)) + .map(_.fold(identity, _.last)) + } + } else None beforeOpenParenSplits.getOrElse(Seq(Split(modification, 0))) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala index d7fe7c3f56..90e5ed132b 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala @@ -472,7 +472,8 @@ object TreeOps { case _ => false } - type CallParts = (Tree, Either[Seq[Tree], Seq[Seq[Tree]]]) + type CallArgs = Either[Seq[Tree], Seq[Seq[Tree]]] + type CallParts = (Tree, CallArgs) val splitCallIntoParts: PartialFunction[Tree, CallParts] = { case t: Term.Apply => (t.fun, Left(t.args)) case t: Term.Super => (t, Left(Seq(t.superp))) diff --git a/scalafmt-tests/src/test/resources/newlines/source_classic.stat b/scalafmt-tests/src/test/resources/newlines/source_classic.stat index c2ee2faa45..4ef70977f2 100644 --- a/scalafmt-tests/src/test/resources/newlines/source_classic.stat +++ b/scalafmt-tests/src/test/resources/newlines/source_classic.stat @@ -5679,3 +5679,34 @@ object a { } else doSomethingOtherwise() } +<<< align.beforeOpenParenDefnSite +maxColumn = 100 +align.closeParenSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} +<<< align.beforeOpenParenDefnSite + align.openParenDefnSite +maxColumn = 85 +danglingParentheses.defnSite = false +align.closeParenSite = true +align.openParenDefnSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String, partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching(versionString: String, + partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} diff --git a/scalafmt-tests/src/test/resources/newlines/source_fold.stat b/scalafmt-tests/src/test/resources/newlines/source_fold.stat index b6779d4531..0368976b2a 100644 --- a/scalafmt-tests/src/test/resources/newlines/source_fold.stat +++ b/scalafmt-tests/src/test/resources/newlines/source_fold.stat @@ -5438,3 +5438,34 @@ object a { } else doSomethingOtherwise() } +<<< align.beforeOpenParenDefnSite +maxColumn = 100 +align.closeParenSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} +<<< align.beforeOpenParenDefnSite + align.openParenDefnSite +maxColumn = 85 +danglingParentheses.defnSite = false +align.closeParenSite = true +align.openParenDefnSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String, partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching(versionString: String, + partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} diff --git a/scalafmt-tests/src/test/resources/newlines/source_keep.stat b/scalafmt-tests/src/test/resources/newlines/source_keep.stat index cf040358ad..6ae5267e71 100644 --- a/scalafmt-tests/src/test/resources/newlines/source_keep.stat +++ b/scalafmt-tests/src/test/resources/newlines/source_keep.stat @@ -5698,3 +5698,33 @@ object a { } else doSomethingOtherwise() } +<<< align.beforeOpenParenDefnSite +maxColumn = 100 +align.closeParenSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = {} +<<< align.beforeOpenParenDefnSite + align.openParenDefnSite +maxColumn = 85 +danglingParentheses.defnSite = false +align.closeParenSite = true +align.openParenDefnSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String, partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching(versionString: String, + partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} diff --git a/scalafmt-tests/src/test/resources/newlines/source_unfold.stat b/scalafmt-tests/src/test/resources/newlines/source_unfold.stat index 5d7ae7ed1d..fa6005b51a 100644 --- a/scalafmt-tests/src/test/resources/newlines/source_unfold.stat +++ b/scalafmt-tests/src/test/resources/newlines/source_unfold.stat @@ -5949,3 +5949,36 @@ object a { else doSomethingOtherwise() } +<<< align.beforeOpenParenDefnSite +maxColumn = 100 +align.closeParenSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching + (versionString: String) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} +<<< align.beforeOpenParenDefnSite + align.openParenDefnSite +maxColumn = 85 +danglingParentheses.defnSite = false +align.closeParenSite = true +align.openParenDefnSite = true +align.beforeOpenParenDefnSite = true +newlines.beforeOpenParenDefnSite = source +=== +def allMatching(versionString: String, partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*): List[String] = { + + } +>>> +def allMatching + (versionString: String, + partialFunctions: PartialFunction[options.Common, List[String]]*) + (partialFunctions: PartialFunction[options.Common, List[String]]*) + : List[String] = {} diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat b/scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat index 63e72f89ba..29a28f266a 100644 --- a/scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat +++ b/scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat @@ -3288,3 +3288,27 @@ object a: do matrix(x)(0) += matrix(x - 1)(0) while x < range do matrix(x)(0) += matrix(x - 1)(0) +<<< align.beforeOpenParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString) + (partialFunctions) +>>> +allMatching(versionString) + (partialFunctions) +<<< align.beforeOpenParenCallSite + align.openParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.openParenCallSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString, partialFunctions) + (partialFunctions) +>>> +allMatching(versionString, + partialFunctions + )(partialFunctions) diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_fold.stat b/scalafmt-tests/src/test/resources/scala3/OptionalBraces_fold.stat index f0d7480da9..e8c7eec695 100644 --- a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_fold.stat +++ b/scalafmt-tests/src/test/resources/scala3/OptionalBraces_fold.stat @@ -3131,3 +3131,27 @@ object a: matrix(x)(0) += matrix(x - 1)(0) while x < range do matrix(x)(0) += matrix(x - 1)(0) +<<< align.beforeOpenParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString) + (partialFunctions) +>>> +allMatching(versionString) + (partialFunctions) +<<< align.beforeOpenParenCallSite + align.openParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.openParenCallSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString, partialFunctions) + (partialFunctions) +>>> +allMatching(versionString, + partialFunctions + )(partialFunctions) diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_keep.stat b/scalafmt-tests/src/test/resources/scala3/OptionalBraces_keep.stat index dfb7f43d4d..89696e19ca 100644 --- a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_keep.stat +++ b/scalafmt-tests/src/test/resources/scala3/OptionalBraces_keep.stat @@ -3276,3 +3276,28 @@ object a: x < range do matrix(x)(0) += matrix(x - 1)(0) +<<< align.beforeOpenParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString) + (partialFunctions) +>>> +allMatching(versionString) + (partialFunctions) +<<< align.beforeOpenParenCallSite + align.openParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.openParenCallSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString, partialFunctions) + (partialFunctions) +>>> +allMatching(versionString, + partialFunctions + ) + (partialFunctions) diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_unfold.stat b/scalafmt-tests/src/test/resources/scala3/OptionalBraces_unfold.stat index df6bb78577..729ec201e8 100644 --- a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_unfold.stat +++ b/scalafmt-tests/src/test/resources/scala3/OptionalBraces_unfold.stat @@ -3376,3 +3376,30 @@ object a: matrix(x)(0) += matrix(x - 1)(0) while x < range do matrix(x)(0) += matrix(x - 1)(0) +<<< align.beforeOpenParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString) + (partialFunctions) +>>> +allMatching + (versionString) + (partialFunctions) +<<< align.beforeOpenParenCallSite + align.openParenCallSite +maxColumn = 30 +align.closeParenSite = true +align.openParenCallSite = true +align.beforeOpenParenCallSite = true +newlines.beforeOpenParenCallSite = source +=== +allMatching(versionString, partialFunctions) + (partialFunctions) +>>> +allMatching + (versionString, + partialFunctions + ) + (partialFunctions)