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: implement spaces.beforeXxxArgInParens #3612

Merged
merged 2 commits into from
Aug 22, 2023
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
65 changes: 65 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4300,6 +4300,71 @@ spaces.afterSymbolicDefs=true
def +++(a: A): F[A]
```

### `spaces.beforeXxxArgInParens`

> Since v3.7.13.

These parameters control whether a space should be added before an argument of
a function call or infix expression, if the argument is enclosed in parentheses.

They take the following values:

- `Never`: no space is added
- `Always`: space is added
- `AfterSymbolic`: space is added if the infix operator or function name is
symbolic (doesn't start with a letter or underscore)

Please note that these parameters will not affect spacing after an
[unary operator](https://docs.scala-lang.org/scala3/reference/changed-features/operators.html#unary-operators)
(i.e., one of `+`, `-`, `!`, `~`), as it's neither a function call nor an infix.

Also, `spaces.beforeApplyArgInParens` generalizes the special-case parameter
`spaces.afterTripleEquals` which only applies to a `===` function call.

```scala mdoc:defaults
spaces.beforeApplyArgInParens
spaces.beforeInfixArgInParens
```

```scala mdoc:scalafmt
spaces.beforeApplyArgInParens = Always
spaces.beforeInfixArgInParens = Always
---
+(baz)
===(baz)
bar(baz)

foo +(baz)
foo ===(baz)
foo bar(baz)
```

```scala mdoc:scalafmt
spaces.beforeApplyArgInParens = Never
spaces.beforeInfixArgInParens = Never
---
+ (baz)
=== (baz)
bar (baz)

foo + (baz)
foo === (baz)
foo bar (baz)
```

```scala mdoc:scalafmt
spaces.beforeApplyArgInParens = AfterSymbolic
spaces.beforeInfixArgInParens = AfterSymbolic
---
+ (baz)
===(baz)
bar (baz)

foo +(baz)
foo ===(baz)
foo bar (baz)
```

## Literals

> Since v2.5.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.scalafmt.config
import scala.meta.tokens.Token

import metaconfig._
import org.scalafmt.util.TokenOps

/** @param beforeContextBoundColon
* formats [A: T] as [A : T]
Expand Down Expand Up @@ -47,6 +48,10 @@ import metaconfig._
case class Spaces(
beforeContextBoundColon: Spaces.BeforeContextBound =
Spaces.BeforeContextBound.Never,
beforeApplyArgInParens: Spaces.BeforeArgInParens =
Spaces.BeforeArgInParens.Never,
beforeInfixArgInParens: Spaces.BeforeArgInParens =
Spaces.BeforeArgInParens.Always,
afterTripleEquals: Boolean = false,
inImportCurlyBraces: Boolean = false,
inInterpolatedStringCurlyBraces: Boolean = false,
Expand Down Expand Up @@ -78,4 +83,25 @@ object Spaces {
case object IfMultipleBounds extends BeforeContextBound
}

sealed abstract class BeforeArgInParens {
def apply(name: => String): Boolean
}
private object BeforeArgInParens {
implicit val codec: ConfCodecEx[BeforeArgInParens] = ReaderUtil
.oneOfCustom[BeforeArgInParens](Never, Always, AfterSymbolic) {
case Conf.Bool(true) => Configured.ok(Always)
case Conf.Bool(false) => Configured.ok(Never)
}

case object Never extends BeforeArgInParens {
def apply(name: => String): Boolean = false
}
case object Always extends BeforeArgInParens {
def apply(name: => String): Boolean = true
}
case object AfterSymbolic extends BeforeArgInParens {
def apply(name: => String): Boolean = TokenOps.isSymbolicName(name)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -477,24 +477,36 @@ class FormatOps(
if style.spaces.neverAroundInfixTypes.contains(t.op.value) =>
Seq(Split(NoSplit, 0))
case t =>
val isBeforeOp = ft.meta.leftOwner ne app.op
def useSpace = style.spaces.beforeInfixArgInParens(app.op.value) ||
(app.arg match {
case _: Lit.Unit => false
case x: Member.ArgClause if x.values.lengthCompare(1) != 0 => false
case x => !isEnclosedInParens(x)
})
val afterInfix = style.breakAfterInfix(t)
if (afterInfix ne Newlines.AfterInfix.keep) {
if (ft.meta.leftOwner ne app.op) Seq(Split(Space, 0))
if (isBeforeOp) Seq(Split(Space, 0))
else {
val spaceMod = Space(useSpace)
val fullInfix = InfixSplits.findEnclosingInfix(app)
val ok = isEnclosedInParens(fullInfix) || fullInfix.parent.forall {
case t: Defn.Val => t.rhs eq fullInfix
case t: Defn.Var => t.body eq fullInfix
case _ => true
}
if (ok)
InfixSplits(app, ft, fullInfix).getBeforeLhsOrRhs(afterInfix)
else Seq(Split(Space, 0))
InfixSplits(app, ft, fullInfix)
.getBeforeLhsOrRhs(afterInfix, spaceMod = spaceMod)
else Seq(Split(spaceMod, 0))
}
} else {
// we don't modify line breaks generally around infix expressions
// TODO: if that ever changes, modify how rewrite rules handle infix
Seq(InfixSplits.withNLIndent(Split(getMod(ft), 0))(app, ft))
val mod = getMod(ft)
val modOrNoSplit =
if (mod != Space || isBeforeOp || useSpace) mod else NoSplit
Seq(InfixSplits.withNLIndent(Split(modOrNoSplit, 0))(app, ft))
}
}

Expand Down Expand Up @@ -700,7 +712,8 @@ class FormatOps(

def getBeforeLhsOrRhs(
afterInfix: Newlines.AfterInfix,
newStmtMod: Option[Modification] = None
newStmtMod: Option[Modification] = None,
spaceMod: Modification = Space
): Seq[Split] = {
val beforeLhs = ft.meta.leftOwner ne app.op
val maxPrecedence =
Expand Down Expand Up @@ -768,7 +781,7 @@ class FormatOps(
.withSingleLine(singleLineExpire)
.andPolicyOpt(singleLinePolicy)
.andPolicyOpt(delayedBreak)
val spaceSingleLine = Split(Space, 0)
val spaceSingleLine = Split(spaceMod, 0)
.onlyIf(newStmtMod.isEmpty)
.withSingleLine(singleLineExpire)
.andPolicyOpt(singleLinePolicy)
Expand Down Expand Up @@ -799,7 +812,7 @@ class FormatOps(
.andPolicyOpt(breakAfterClose)
.withIndent(nlIndent)
.withPolicy(nlPolicy)
val singleLineSplit = Split(Space, 0)
val singleLineSplit = Split(spaceMod, 0)
.notIf(noSingleLine)
.withSingleLine(endOfNextOp.fold(close)(_.left))
.andPolicyOpt(breakAfterClose)
Expand All @@ -817,7 +830,7 @@ class FormatOps(
val exclude =
if (breakMany) TokenRanges.empty
else insideBracesBlock(nextFT, expire, true)
Split(ModExt(newStmtMod.getOrElse(Space)), cost)
Split(ModExt(newStmtMod.getOrElse(spaceMod)), cost)
.withSingleLine(expire, exclude)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,8 @@ class Router(formatOps: FormatOps) {
case Term.Name(name) =>
style.spaces.afterTripleEquals && name == "===" ||
(rightOwner match {
case _: Term.ArgClause =>
style.spaces.beforeApplyArgInParens(name)
case _: Member.ParamClause =>
style.spaces.afterSymbolicDefs && isSymbolicName(name)
case _ => false
Expand Down Expand Up @@ -1944,7 +1946,7 @@ class Router(formatOps: FormatOps) {
Seq(
Split(NoSplit, 0)
)
case FormatToken(op @ T.Ident(_), right, _) if leftOwner.parent.exists {
case FormatToken(op: T.Ident, right, _) if leftOwner.parent.exists {
case unary: Term.ApplyUnary =>
unary.op.tokens.head == op
case _ => false
Expand Down
78 changes: 78 additions & 0 deletions scalafmt-tests/src/test/resources/unit/Apply.stat
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,81 @@ test("foo " +
case baz =>
case qux =>
}
<<< #3607 beforeApplyArgInParens=always
spaces.beforeApplyArgInParens = always
===
object a {
+ ()
=== ()
bar ()
+ (baz)
=== (baz)
bar (baz)
+ (baz, qux)
=== (baz, qux)
bar (baz, qux)
}
>>>
object a {
+()
=== ()
bar ()
+(baz)
=== (baz)
bar (baz)
+(baz, qux)
=== (baz, qux)
bar (baz, qux)
}
<<< #3607 beforeApplyArgInParens=never
spaces.beforeApplyArgInParens = never
===
object a {
+ ()
=== ()
bar ()
+ (baz)
=== (baz)
bar (baz)
+ (baz, qux)
=== (baz, qux)
bar (baz, qux)
}
>>>
object a {
+()
===()
bar()
+(baz)
===(baz)
bar(baz)
+(baz, qux)
===(baz, qux)
bar(baz, qux)
}
<<< #3607 beforeApplyArgInParens=aftersymbolic
spaces.beforeApplyArgInParens = aftersymbolic
===
object a {
+ ()
=== ()
bar ()
+ (baz)
=== (baz)
bar (baz)
+ (baz, qux)
=== (baz, qux)
bar (baz, qux)
}
>>>
object a {
+()
=== ()
bar()
+(baz)
=== (baz)
bar(baz)
+(baz, qux)
=== (baz, qux)
bar(baz, qux)
}
96 changes: 96 additions & 0 deletions scalafmt-tests/src/test/resources/unit/ApplyInfix.stat
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,99 @@ type x = (X
type x = (X
:: Y
:: Z)
<<< #3607 beforeInfixArgInParens=always
spaces.beforeInfixArgInParens = always
===
object a {
foo + baz
foo === baz
foo bar baz
foo + ()
foo === ()
foo bar ()
foo + (baz)
foo === (baz)
foo bar (baz)
foo + (baz, qux)
foo === (baz, qux)
foo bar (baz, qux)
}
>>>
object a {
foo + baz
foo === baz
foo bar baz
foo + ()
foo === ()
foo bar ()
foo + (baz)
foo === (baz)
foo bar (baz)
foo + (baz, qux)
foo === (baz, qux)
foo bar (baz, qux)
}
<<< #3607 beforeInfixArgInParens=never
spaces.beforeInfixArgInParens = never
===
object a {
foo + baz
foo === baz
foo bar baz
foo + ()
foo === ()
foo bar ()
foo + (baz)
foo === (baz)
foo bar (baz)
foo + (baz, qux)
foo === (baz, qux)
foo bar (baz, qux)
}
>>>
object a {
foo + baz
foo === baz
foo bar baz
foo +()
foo ===()
foo bar()
foo +(baz)
foo ===(baz)
foo bar(baz)
foo +(baz, qux)
foo ===(baz, qux)
foo bar(baz, qux)
}
<<< #3607 beforeInfixArgInParens=aftersymbolic
spaces.beforeInfixArgInParens = aftersymbolic
===
object a {
foo + baz
foo === baz
foo bar baz
foo + ()
foo === ()
foo bar ()
foo + (baz)
foo === (baz)
foo bar (baz)
foo + (baz, qux)
foo === (baz, qux)
foo bar (baz, qux)
}
>>>
object a {
foo + baz
foo === baz
foo bar baz
foo + ()
foo === ()
foo bar()
foo + (baz)
foo === (baz)
foo bar(baz)
foo + (baz, qux)
foo === (baz, qux)
foo bar(baz, qux)
}
Loading