diff --git a/docs/configuration.md b/docs/configuration.md index 05db3665d..daaa02707 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3025,6 +3025,9 @@ The rule takes the following parameters under `rewrite.avoidInfix`: be excluded - (since 3.8.4) `excludePostfix`, unless set to `true` explicitly, will also apply the rule to `Term.Select` trees specified without a dot +- (since 3.8.4) `excludeMatch`, if set to `false` explicitly and if the dialect + enables `allowMatchAsOperator` (such as Scala 3), will also apply the rule to + `Term.Match` trees specified without a dot ```scala mdoc:scalafmt rewrite.rules = [AvoidInfix] @@ -5142,6 +5145,7 @@ object A { This option will enforce a break before each parent. As usual, the break is only actually introduced if indented position on the next line is less than the current. +Added in 3.8.4. ```scala mdoc:scalafmt binPack.parentConstructors = ForceBreak diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/AvoidInfixSettings.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/AvoidInfixSettings.scala index 30d1dae1e..2c09f3359 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/AvoidInfixSettings.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/AvoidInfixSettings.scala @@ -14,6 +14,7 @@ case class AvoidInfixSettings( private val excludeScalaTest: Option[Boolean] = None, excludePlaceholderArg: Option[Boolean] = None, excludePostfix: Boolean = false, + excludeMatch: Boolean = true, ) { // if the user completely redefined (rather than appended), we don't touch @inline diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala index 04da7e6fd..a42e26064 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/AvoidInfix.scala @@ -21,6 +21,7 @@ object AvoidInfix extends RewriteFactory { class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { private val cfg = ctx.style.rewrite.avoidInfix + private val allowMatchAsOperator = dialect.allowMatchAsOperator // In a perfect world, we could just use // Tree.transform { @@ -30,18 +31,32 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { // we will do these dangerous rewritings by hand. override def rewrite(tree: Tree): Unit = tree match { - case x: Term.ApplyInfix => rewriteImpl(x.lhs, x.op, x.arg, x.targClause) + case x: Term.ApplyInfix => + rewriteImpl(x.lhs, Right(x.op), x.arg, x.targClause) case x: Term.Select if !cfg.excludePostfix && noDot(x.name.tokens.head) => - rewriteImpl(x.qual, x.name) + rewriteImpl(x.qual, Right(x.name)) + case x: Term.Match => noDotMatch(x) + .foreach(op => rewriteImpl(x.expr, Left(op), null)) case _ => } private def noDot(opToken: T): Boolean = !ctx.tokenTraverser.prevNonTrivialToken(opToken).forall(_.is[T.Dot]) + private def noDotMatch(t: Term.Match): Either[Boolean, T] = + if (allowMatchAsOperator && t.mods.isEmpty) + ctx.tokenTraverser.prevNonTrivialToken(t.casesBlock.tokens.head) match { + case Some(kw) => + if (!noDot(kw)) Left(true) + else if (cfg.excludeMatch) Left(false) + else Right(kw) + case _ => Left(false) + } + else Left(false) + private def rewriteImpl( lhs: Term, - op: Name, + op: Either[T, Name], rhs: Tree = null, targs: Member.SyntaxValuesClause = null, ): Unit = { @@ -52,19 +67,24 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { val lhsIsOK = lhsIsWrapped || (lhs match { case t: Term.ApplyInfix => checkMatchingInfix(t.lhs, t.op.value, t.arg) + case t: Term.Match => noDotMatch(t) match { + case Left(ok) => ok + case Right(kw) => checkMatchingInfix(t.expr, kw.text, t.casesBlock) + } case _ => false }) - if (!checkMatchingInfix(lhs, op.value, rhs, Some(lhsIsOK))) return + if (!checkMatchingInfix(lhs, op.fold(_.text, _.value), rhs, Some(lhsIsOK))) + return if (!ctx.dialect.allowTryWithAnyExpr) if (beforeLhsHead.exists(_.is[T.KwTry])) return val builder = Seq.newBuilder[TokenPatch] - val (opHead, opLast) = ends(op) + val (opHead, opLast) = op.fold((_, null), ends) builder += TokenPatch.AddLeft(opHead, ".", keepTok = true) - if (rhs ne null) { + if ((rhs ne null) && (opLast ne null)) { def moveOpenDelim(prev: T, open: T): Unit = { // move delimiter (before comment or newline) builder += TokenPatch.AddRight(prev, open.text, keepTok = true) @@ -94,7 +114,8 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { case _: Term.ApplyInfix | _: Term.Match => !lhsIsOK // foo _ compose bar => (foo _).compose(bar) // new Foo compose bar => (new Foo).compose(bar) - case _: Term.Eta | _: Term.New => true + case _: Term.Eta | _: Term.New | _: Term.Annotate => true + case t: Term.Select if rhs eq null => !noDot(t.name.tokens.head) case _ => false }) if (shouldWrapLhs) { @@ -124,6 +145,15 @@ class AvoidInfix(implicit ctx: RewriteCtx) extends RewriteSession { case None => isWrapped(lhs) || checkMatchingInfix(lhs.lhs, lhs.op.value, lhs.arg) } + case lhs: Term.Match if hasPlaceholder(lhs, includeArg = true) => + lhsIsOK match { + case Some(x) => x + case None if isWrapped(lhs) => true + case None => noDotMatch(lhs) match { + case Left(ok) => ok + case Right(op) => checkMatchingInfix(lhs.expr, op.text, null) + } + } case _ => true }) diff --git a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces.stat index 117c6d8c8..703f0ad39 100644 --- a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces.stat +++ b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces.stat @@ -7564,8 +7564,9 @@ object Build: } }.evaluated ) -<<< AvoidInfix with match +<<< AvoidInfix with match, excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match @@ -7576,8 +7577,23 @@ object a { a.b(c) match case _ => } -<<< AvoidInfix with match within applyinfix +<<< AvoidInfix with match, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match + case _ => +} +>>> +object a { + a.b(c) + .match + case _ => +} +<<< AvoidInfix with match within applyinfix, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match { @@ -7590,8 +7606,39 @@ object a { case _ => }).d(e) } -<<< AvoidInfix with match, with dot +<<< AvoidInfix with match within applyinfix, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match { + case _ => + } d e +} +>>> +object a { + a.b(c) + .match { case _ => + } + .d(e) +} +<<< AvoidInfix with match, with dot, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true +=== +object a { + (a.b(c)).match + case _ => +} +>>> +object a { + (a.b(c)) + .match + case _ => +} +<<< AvoidInfix with match, with dot, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false === object a { (a.b(c)).match @@ -7603,6 +7650,32 @@ object a { .match case _ => } +<<< AvoidInfix with match, with annotation, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + a(b): @c match + case _ => +} +<<< AvoidInfix with match, with annotation, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + (a(b): @c).match + case _ => +} <<< match with dot, trailing case comment object a: b.c.match diff --git a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_fold.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_fold.stat index ed8f22d5a..1ad57b477 100644 --- a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_fold.stat +++ b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_fold.stat @@ -7275,8 +7275,9 @@ object Build: config: GenerationConfig => config.remove[SiteRoot] } }.evaluated) -<<< AvoidInfix with match +<<< AvoidInfix with match, excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match @@ -7287,8 +7288,22 @@ object a { a.b(c) match case _ => } -<<< AvoidInfix with match within applyinfix +<<< AvoidInfix with match, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match + case _ => +} +>>> +object a { + a.b(c).match + case _ => +} +<<< AvoidInfix with match within applyinfix, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match { @@ -7299,8 +7314,22 @@ object a { object a { (a.b(c) match { case _ => }).d(e) } -<<< AvoidInfix with match, with dot +<<< AvoidInfix with match within applyinfix, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match { + case _ => + } d e +} +>>> +object a { + a.b(c).match { case _ => }.d(e) +} +<<< AvoidInfix with match, with dot, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { (a.b(c)).match @@ -7311,6 +7340,45 @@ object a { (a.b(c)).match case _ => } +<<< AvoidInfix with match, with dot, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + (a.b(c)).match + case _ => +} +>>> +object a { + (a.b(c)).match + case _ => +} +<<< AvoidInfix with match, with annotation, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + a(b): @c match + case _ => +} +<<< AvoidInfix with match, with annotation, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + (a(b): @c).match + case _ => +} <<< match with dot, trailing case comment object a: b.c.match diff --git a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_keep.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_keep.stat index 7c8601f8d..606a66c14 100644 --- a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_keep.stat +++ b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_keep.stat @@ -7592,8 +7592,9 @@ object Build: } }.evaluated ) -<<< AvoidInfix with match +<<< AvoidInfix with match, excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match @@ -7604,8 +7605,22 @@ object a { a.b(c) match case _ => } -<<< AvoidInfix with match within applyinfix +<<< AvoidInfix with match, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match + case _ => +} +>>> +object a { + a.b(c).match + case _ => +} +<<< AvoidInfix with match within applyinfix, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match { @@ -7618,8 +7633,37 @@ object a { case _ => }).d(e) } -<<< AvoidInfix with match, with dot +<<< AvoidInfix with match within applyinfix, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match { + case _ => + } d e +} +>>> +object a { + a.b(c).match { + case _ => + }.d(e) +} +<<< AvoidInfix with match, with dot, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true +=== +object a { + (a.b(c)).match + case _ => +} +>>> +object a { + (a.b(c)).match + case _ => +} +<<< AvoidInfix with match, with dot, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false === object a { (a.b(c)).match @@ -7630,6 +7674,32 @@ object a { (a.b(c)).match case _ => } +<<< AvoidInfix with match, with annotation, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + a(b): @c match + case _ => +} +<<< AvoidInfix with match, with annotation, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + (a(b): @c).match + case _ => +} <<< match with dot, trailing case comment object a: b.c.match diff --git a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_unfold.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_unfold.stat index fd57e5dc8..f553bec71 100644 --- a/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_unfold.stat +++ b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_unfold.stat @@ -7879,8 +7879,9 @@ object Build: } .evaluated ) -<<< AvoidInfix with match +<<< AvoidInfix with match, excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match @@ -7891,8 +7892,23 @@ object a { a.b(c) match case _ => } -<<< AvoidInfix with match within applyinfix +<<< AvoidInfix with match, !excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match + case _ => +} +>>> +object a { + a.b(c) + .match + case _ => +} +<<< AvoidInfix with match within applyinfix, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { a b c match { @@ -7907,8 +7923,25 @@ object a { } ).d(e) } -<<< AvoidInfix with match, with dot +<<< AvoidInfix with match within applyinfix, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a b c match { + case _ => + } d e +} +>>> +object a { + a.b(c) + .match { case _ => + } + .d(e) +} +<<< AvoidInfix with match, with dot, excludeMatch rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true === object a { (a.b(c)).match @@ -7919,6 +7952,45 @@ object a { (a.b(c)).match case _ => } +<<< AvoidInfix with match, with dot, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + (a.b(c)).match + case _ => +} +>>> +object a { + (a.b(c)).match + case _ => +} +<<< AvoidInfix with match, with annotation, excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = true +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + a(b): @c match + case _ => +} +<<< AvoidInfix with match, with annotation, !excludeMatch +rewrite.rules = [AvoidInfix] +rewrite.avoidInfix.excludeMatch = false +=== +object a { + a(b): @c match + case _ => +} +>>> +object a { + (a(b): @c).match + case _ => +} <<< match with dot, trailing case comment object a: b.c.match diff --git a/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala index fe052f867..168719666 100644 --- a/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala @@ -144,7 +144,7 @@ class FormatTests extends FunSuite with CanRunTests with FormatAssertions { val explored = Debug.explored.get() logger.debug(s"Total explored: $explored") if (!onlyUnit && !onlyManual) - assertEquals(explored, 1083760, "total explored") + assertEquals(explored, 1084630, "total explored") val results = debugResults.result() // TODO(olafur) don't block printing out test results. // I don't want to deal with scalaz's Tasks :'(