diff --git a/build.sbt b/build.sbt index dce2b6a4a2..9b86615164 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,7 @@ lazy val buildSettings = Seq( libraryDependencies += scalatest.value % Test, triggeredMessage in ThisBuild := Watched.clearWhenTriggered, scalacOptions in (Compile, console) := compilerOptions :+ "-Yrepl-class-based", + scalacOptions in (Compile, console) --= Seq("-Xlint", "-Ywarn-dead-code"), assemblyJarName in assembly := "scalafmt.jar", testOptions in Test += Tests.Argument("-oD") ) diff --git a/project/plugins.sbt b/project/plugins.sbt index c6155fb6aa..b81f760249 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,7 +6,8 @@ resolvers ++= Seq( addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") addSbtPlugin("com.dwijnand" % "sbt-dynver" % "1.2.0") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") -addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC12") +addSbtPlugin( + "io.get-coursier" % "sbt-coursier" % coursier.util.Properties.version) addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.27") diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt index 72337a51dd..ef1f82c556 100644 --- a/project/project/plugins.sbt +++ b/project/project/plugins.sbt @@ -1 +1 @@ -addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC3") +addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.2") diff --git a/readme/Changelog10.scalatex b/readme/Changelog10.scalatex index 92a09e82a6..6d0ba93dbe 100644 --- a/readme/Changelog10.scalatex +++ b/readme/Changelog10.scalatex @@ -7,6 +7,13 @@ @ul @li Please document your contribution here. + @li + @pr(1122) Update @code{RedundantBraces} to remove redundant braces from + all expressions. If you'd like to restrict @code{RedundantBraces} behaviour + to just method bodies and/or string interpolation as it was before this + change, you can do so via new setting: + @code{rewrite.redundantBraces.generalExpressions = false} + By @user{japgolly}. @sect{1.4.0} @p See @lnk("merged PRs", "https://github.com/scalameta/scalafmt/milestone/19?closed=1"). diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/RedundantBracesSettings.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/RedundantBracesSettings.scala index cda2a634f9..07525dbc9c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/RedundantBracesSettings.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/RedundantBracesSettings.scala @@ -4,7 +4,8 @@ import metaconfig._ @DeriveConfDecoder case class RedundantBracesSettings( + methodBodies: Boolean = true, includeUnitMethods: Boolean = true, maxLines: Int = 100, - stringInterpolation: Boolean = false -) + stringInterpolation: Boolean = false, + generalExpressions: Boolean = true) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Side.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Side.scala new file mode 100644 index 0000000000..278ac37db8 --- /dev/null +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Side.scala @@ -0,0 +1,10 @@ +package org.scalafmt.internal + +sealed abstract class Side { + def isLeft: Boolean = this == Side.Left +} + +object Side { + case object Right extends Side + case object Left extends Side +} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SyntacticGroup.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SyntacticGroup.scala new file mode 100644 index 0000000000..8f098fa610 --- /dev/null +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SyntacticGroup.scala @@ -0,0 +1,71 @@ +package org.scalafmt.internal + +import scala.meta.Tree + +sealed trait SyntacticGroup { + def categories: List[String] + def precedence: Double +} + +object SyntacticGroup { + sealed trait Type extends SyntacticGroup { + def categories = List("Type") + } + object Type { + case object ParamTyp extends Type { def precedence = 0 } + case object Typ extends Type { def precedence = 1 } + case object AnyInfixTyp extends Type { def precedence = 1.5 } + case class InfixTyp(operator: String) extends Type { def precedence = 2 } + case object RefineTyp extends Type { def precedence = 3 } + case object WithTyp extends Type { def precedence = 3.5 } + case object AnnotTyp extends Type { def precedence = 4 } + case object SimpleTyp extends Type { def precedence = 6 } + } + sealed trait Term extends SyntacticGroup { + def categories = List("Term") + } + object Term { + case object Expr extends Term { def precedence = 0 } + case object Expr1 extends Term { def precedence = 1 } + case object Ascription extends Term { def precedence = 2 } + case object PostfixExpr extends Term { def precedence = 2 } + case class InfixExpr(operator: String) extends Term { def precedence = 3 } + case class PrefixExpr(operator: String) extends Term { def precedence = 4 } + object PrefixArg { + def apply(tree: Tree): PrefixArg = + PrefixArg(tree, TreeSyntacticGroup(tree)) + } + case class PrefixArg(tree: Tree, innerGroup: SyntacticGroup) extends Term { + def precedence = innerGroup.precedence + } + case object SimpleExpr extends Term { def precedence = 5 } + case object SimpleExpr1 extends Term { def precedence = 6 } + } + sealed trait Pat extends SyntacticGroup { + def categories = List("Pat") + } + object Pat { + case object Pattern extends Pat { def precedence = 0 } + case object Pattern1 extends Pat { def precedence = 1 } + case object Pattern2 extends Pat { def precedence = 2 } + case object AnyPattern3 extends Pat { def precedence = 2.5 } + case class Pattern3(operator: String) extends Pat { def precedence = 3 } + case object SimplePattern extends Pat { def precedence = 6 } + } + case object Literal extends Term with Pat { + override def categories = List("Term", "Pat"); def precedence = 6 + } + require( + Literal.precedence == Term.SimpleExpr1.precedence && + Literal.precedence == Pat.SimplePattern.precedence + ) + case object Path extends Type with Term with Pat { + override def categories = List("Type", "Term", "Pat"); + def precedence = 6 + } + require( + Path.precedence == Type.SimpleTyp.precedence && + Path.precedence == Term.SimpleExpr1.precedence && + Path.precedence == Pat.SimplePattern.precedence + ) +} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SyntacticGroupOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SyntacticGroupOps.scala new file mode 100644 index 0000000000..cb975e1fa6 --- /dev/null +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SyntacticGroupOps.scala @@ -0,0 +1,101 @@ +package org.scalafmt.internal + +import scala.meta.{Term, Tree, Lit} +import org.scalafmt.internal.{SyntacticGroup => g} +import scala.meta.internal.ast.Helpers._ + +object SyntacticGroupOps { + + def operatorNeedsParenthesis( + outerOperator: String, + innerOperator: String, + customAssociativity: Boolean, + customPrecedence: Boolean, + side: Side, + forceRight: Boolean = false + ): Boolean = { + + // The associativity of an operator is determined by the operator's last character. + // Operators ending in a colon ‘:’ are right-associative. All + // other operators are left-associative. + // https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#infix-operations + def isLeftAssociative(name: String): Boolean = + if (customAssociativity) name.last != ':' else true + + def precedence(name: String): Int = + if (customPrecedence) Term.Name(name).precedence else 0 + + val outerOperatorIsLeftAssociative = isLeftAssociative(outerOperator) + val innerOperatorIsLeftAssociative = isLeftAssociative(innerOperator) + + if (outerOperatorIsLeftAssociative ^ innerOperatorIsLeftAssociative) true + else { + val isLeft = outerOperatorIsLeftAssociative + val isRight = !outerOperatorIsLeftAssociative + + val outerOperatorPrecedence = precedence(outerOperator) + val innerOperatorPrecedence = precedence(innerOperator) + + if (outerOperatorPrecedence < innerOperatorPrecedence) { + isRight + } else if (outerOperatorPrecedence == innerOperatorPrecedence) { + isLeft ^ side.isLeft + } else { + isLeft || forceRight + } + } + } + + def startsWithNumericLiteral(tree: Tree): Boolean = { + tree match { + case _: Lit.Int | _: Lit.Long | _: Lit.Double | _: Lit.Float | + _: Lit.Byte | _: Lit.Short => + true + case Term.Select(tree0, _) => startsWithNumericLiteral(tree0) + case _ => false + } + } + + def groupNeedsParenthesis( + outerGroup: SyntacticGroup, + innerGroup: SyntacticGroup, + side: Side + ): Boolean = (outerGroup, innerGroup) match { + case (g.Term.InfixExpr(outerOperator), g.Term.InfixExpr(innerOperator)) => + operatorNeedsParenthesis( + outerOperator, + innerOperator, + customAssociativity = true, + customPrecedence = true, + side, + forceRight = true + ) + case (g.Type.InfixTyp(outerOperator), g.Type.InfixTyp(innerOperator)) => + operatorNeedsParenthesis( + outerOperator, + innerOperator, + customAssociativity = true, + customPrecedence = false, + side + ) + case (g.Pat.Pattern3(outerOperator), g.Pat.Pattern3(innerOperator)) => + operatorNeedsParenthesis( + outerOperator, + innerOperator, + customAssociativity = true, + customPrecedence = true, + side + ) + + case (_: g.Term.PrefixExpr, g.Term.PrefixArg(_, _: g.Term.PrefixExpr)) => + true + + case (g.Term.PrefixExpr("-"), g.Term.PrefixArg(Term.Select(tree, _), _)) + if startsWithNumericLiteral(tree) => + true + + case _ => + outerGroup.precedence > innerGroup.precedence + } + +} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala new file mode 100644 index 0000000000..fedc3bb30a --- /dev/null +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/TreeSyntacticGroup.scala @@ -0,0 +1,81 @@ +package org.scalafmt.internal + +import scala.meta.Lit +import scala.meta.Pat +import scala.meta.Term +import scala.meta.Tree +import scala.meta.Type +import org.scalafmt.internal.{SyntacticGroup => g} + +object TreeSyntacticGroup { + def apply(tree: Tree): SyntacticGroup = tree match { + case _: Lit => g.Literal + // Term + case _: Term.Name => g.Path + case _: Term.Select => g.Path + case _: Term.Interpolate => g.Term.SimpleExpr1 + case _: Term.Xml => g.Term.SimpleExpr1 + case _: Term.Apply => g.Term.SimpleExpr1 + case _: Term.ApplyType => g.Term.SimpleExpr1 + case t: Term.ApplyInfix => g.Term.InfixExpr(t.op.value) + case t: Term.ApplyUnary => g.Term.PrefixExpr(t.op.value) + case _: Term.Assign => g.Term.Expr1 + case _: Term.Return => g.Term.Expr1 + case _: Term.Throw => g.Term.Expr1 + case _: Term.Ascribe => g.Term.Expr1 + case _: Term.Annotate => g.Term.Expr1 + case _: Term.Block => g.Term.SimpleExpr1 + case _: Term.Tuple => g.Term.SimpleExpr1 // ???, breaks a op ((b, c)) +// case _: Term.Tuple => g.Term.Expr1 // ??? Was SimpleExpr1, which is buggy for `a op ((b, c)) + case _: Term.If => g.Term.Expr1 + case _: Term.Match => g.Term.Expr1 + case _: Term.TryWithCases => g.Term.Expr1 + case _: Term.TryWithTerm => g.Term.Expr1 + case _: Term.Function => g.Term.Expr + case _: Term.PartialFunction => g.Term.SimpleExpr + case _: Term.While => g.Term.Expr1 + case _: Term.Do => g.Term.Expr1 + case _: Term.For => g.Term.Expr1 + case _: Term.ForYield => g.Term.Expr1 + case _: Term.New => g.Term.SimpleExpr + case _: Term.Placeholder => g.Term.SimpleExpr1 + case _: Term.Eta => g.Term.SimpleExpr + case _: Term.Arg.Repeated => g.Term.PostfixExpr + case _: Term.Param => g.Path // ??? + // Type + case _: Type.Name => g.Path + case _: Type.Select => g.Type.SimpleTyp + case _: Type.Project => g.Type.SimpleTyp + case _: Type.Singleton => g.Type.SimpleTyp + case _: Type.Apply => g.Type.SimpleTyp + case t: Type.ApplyInfix => g.Type.InfixTyp(t.op.value) + case _: Type.Function => g.Type.Typ + case _: Type.Tuple => g.Type.SimpleTyp + case _: Type.With => g.Type.WithTyp + case _: Type.And => g.Type.InfixTyp("&") + case _: Type.Or => g.Type.InfixTyp("|") + case _: Type.Refine => g.Type.RefineTyp + case _: Type.Existential => g.Type.Typ + case _: Type.Annotate => g.Type.AnnotTyp + case _: Type.Placeholder => g.Type.SimpleTyp + case _: Type.Bounds => g.Path // ??? + case _: Type.Arg.Repeated => g.Type.ParamTyp + case _: Type.Arg.ByName => g.Type.ParamTyp + case _: Pat.Var.Type => g.Type.ParamTyp + case _: Type.Param => g.Path // ??? + // Pat + case _: Pat.Var => g.Pat.SimplePattern + case _: Pat.Wildcard => g.Pat.SimplePattern + case _: Pat.Bind => g.Pat.Pattern2 + case _: Pat.Alternative => g.Pat.Pattern + case _: Pat.Tuple => g.Pat.SimplePattern + case _: Pat.Extract => g.Pat.SimplePattern + case t: Pat.ExtractInfix => g.Pat.Pattern3(t.op.value) + case _: Pat.Interpolate => g.Pat.SimplePattern + case _: Pat.Xml => g.Pat.SimplePattern + case _: Pat.Typed => g.Pat.Pattern1 + + // Misc + case _ => g.Path + } +} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala index 7cca397e22..25212f637c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala @@ -1,11 +1,14 @@ package org.scalafmt.rewrite +import org.scalafmt.config.RedundantBracesSettings +import org.scalafmt.internal.Side +import org.scalafmt.internal.SyntacticGroupOps +import org.scalafmt.internal.TreeSyntacticGroup import scala.meta.Tree import scala.meta._ import scala.meta.tokens.Token.LF import scala.meta.tokens.Token.LeftBrace import scala.meta.tokens.Token.RightBrace - import org.scalafmt.util.TreeOps._ /** @@ -13,75 +16,152 @@ import org.scalafmt.util.TreeOps._ */ case object RedundantBraces extends Rewrite { - def isCandidate(d: Defn.Def, ctx: RewriteCtx): Boolean = { - import ctx.style.rewrite.{redundantBraces => settings} - def isBlock = d.body match { - case Term.Block(Seq(stat)) => - (stat match { - case _: Term.Block | _: Term.Function | _: Defn => false - case _ => true - }) && - d.body.tokens.head.is[LeftBrace] && - d.body.tokens.last.is[RightBrace] - case _ => false - } + private type PatchBuilder = + scala.collection.mutable.Builder[Patch, Seq[Patch]] - def disqualifiedByUnit = - !settings.includeUnitMethods && d.decltpe.exists(_.syntax == "Unit") - - def bodyIsNotTooBig: Boolean = - d.body match { - case t: Term.Block => - val stat = t.stats.head - val diff = - stat.tokens.last.pos.end.line - - stat.tokens.head.pos.start.line - diff < settings.maxLines - case _ => false - } + @inline private def settings( + implicit ctx: RewriteCtx): RedundantBracesSettings = + ctx.style.rewrite.redundantBraces - !isProcedureSyntax(d) && - isBlock && - !disqualifiedByUnit && - bodyIsNotTooBig - } + private def processInterpolation(t: Term.Interpolate)( + implicit builder: PatchBuilder, + ctx: RewriteCtx): Unit = { + import ctx.tokenTraverser._ - def isIdentifierAtStart(value: String) = - value.nonEmpty && (Character.isLetterOrDigit(value.head) || value.head == '_') + def isIdentifierAtStart(value: String) = + value.nonEmpty && (Character.isLetterOrDigit(value.head) || value.head == '_') + + t.parts.tail.zip(t.args).foreach { + case (Lit(value: String), arg @ Term.Name(_)) + if !isIdentifierAtStart(value) => + val openBrace = prevToken(arg.tokens.head) + val closeBrace = nextToken(arg.tokens.head) + (openBrace, closeBrace) match { + case (LeftBrace(), RightBrace()) => + builder += TokenPatch.Remove(openBrace) + builder += TokenPatch.Remove(closeBrace) + case _ => + } + case _ => + } + } override def rewrite(code: Tree, ctx: RewriteCtx): Seq[Patch] = { - import ctx.tokenTraverser._ - import ctx.style.rewrite.{redundantBraces => settings} - val builder = Seq.newBuilder[Patch] - code.collect { + implicit def _ctx = ctx + + implicit val builder = Seq.newBuilder[Patch] + + code.traverse { + + case b: Term.Block => + processBlock(b) + case t: Term.Interpolate if settings.stringInterpolation => - t.parts.tail.zip(t.args).collect { - case (Lit(value: String), arg @ Term.Name(name)) - if !isIdentifierAtStart(value) => - val openBrace = prevToken(arg.tokens.head) - val closeBrace = nextToken(arg.tokens.head) - (openBrace, closeBrace) match { - case (LeftBrace(), RightBrace()) => - builder += TokenPatch.Remove(openBrace) - builder += TokenPatch.Remove(closeBrace) - case _ => - } - } - case d: Defn.Def if isCandidate(d, ctx) => - val open = d.body.tokens.head - val close = d.body.tokens.last - val bodyStatement = d.body match { - case t: Term.Block => t.stats.head - case _ => d.body + processInterpolation(t) + } + + builder.result() + } + + private def removeTrailingLF(bodyEnd: Position, close: Token)( + implicit builder: PatchBuilder, + ctx: RewriteCtx): Unit = + if (close.pos.start.line != bodyEnd.end.line) { + import ctx.tokenTraverser._ + val next = nextToken(close) + if (next.is[LF]) + builder += TokenPatch.Remove(next) + } + + private def processBlock( + b: Term.Block)(implicit builder: PatchBuilder, ctx: RewriteCtx): Unit = + if (b.tokens.nonEmpty) { + val open = b.tokens.head + if (open.is[LeftBrace]) { + val close = b.tokens.last + if (removeBlock(b) && close.is[RightBrace]) { + val endPos = if (b.stats.isEmpty) b.pos else b.stats.last.pos + removeTrailingLF(endPos, close) + builder += TokenPatch.Remove(open) + builder += TokenPatch.Remove(close) } - val next = nextToken(close) - if (next.is[LF] && - close.pos.start.line != bodyStatement.pos.end.line) - builder += TokenPatch.Remove(next) + } + } + + private def removeBlock(b: Term.Block)(implicit ctx: RewriteCtx): Boolean = { + def exactlyOneStatement = b.stats.lengthCompare(1) == 0 + b.parent.exists { + + case _: Case => + settings.generalExpressions + + case _: Term.Apply => + // Example: as.map { _.toString } + // Leave this alone for now. + // In future there should be an option to surround such expressions with parens instead of braces + false - builder += TokenPatch.Remove(open) - builder += TokenPatch.Remove(close) + case d: Defn.Def => + def disqualifiedByUnit = + !settings.includeUnitMethods && d.decltpe.exists(_.syntax == "Unit") + def innerOk = + b.stats.head match { + case _: Term.Function | _: Defn => false + case _ => true + } + settings.methodBodies && + exactlyOneStatement && + blockSizeIsOk(b) && + innerOk && + !isProcedureSyntax(d) && + !disqualifiedByUnit + + case _ => + settings.generalExpressions && + exactlyOneStatement && + blockSizeIsOk(b) && + !retainSingleStatBlock(b) } - builder.result() } + + /** Some blocks look redundant but aren't */ + private def retainSingleStatBlock(b: Term.Block): Boolean = + b.parent.exists { + case parentIf: Term.If => + // if (a) { if (b) c } else d + // ↑ cannot be replaced by ↓ + // if (a) if (b) c else d + // which would be equivalent to + // if (a) { if (b) c else d } + def insideIfThen = parentIf.thenp eq b + def parentIfHasAnElse = parentIf.elsep.tokens.nonEmpty + def blockIsIfWithoutElse = b.stats.head match { + case childIf: Term.If => childIf.elsep.tokens.isEmpty + case _ => false + } + insideIfThen && parentIfHasAnElse && blockIsIfWithoutElse + + case parent => + val side = parent match { + case t: Term.ApplyInfix + if t.args.lengthCompare(1) == 0 && (t.args.head eq b) => + Side.Right + case _ => Side.Left + } + SyntacticGroupOps.groupNeedsParenthesis( + TreeSyntacticGroup(parent), + TreeSyntacticGroup(b.stats.head), + side + ) + } + + private def blockSizeIsOk(b: Term.Block)(implicit ctx: RewriteCtx): Boolean = + b.tokens.isEmpty || { + val diff = + if (b.stats.isEmpty) + b.tokens.last.pos.end.line - b.tokens.head.pos.start.line + else + b.stats.last.pos.end.line - b.stats.head.pos.start.line + diff <= settings.maxLines + } } diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-case.stat b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-case.stat new file mode 100644 index 0000000000..d80933727d --- /dev/null +++ b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-case.stat @@ -0,0 +1,76 @@ +rewrite.rules = [RedundantBraces] +<<< case clauses +object a { + + val x: Int => String = { + case 2 => { + "two" + } + case _ => { + "other" + } + } + +} +>>> +object a { + + val x: Int => String = { + case 2 => + "two" + case _ => + "other" + } + +} +<<< case clauses with stuff +object a { + + val x: Int => String = { + case 2 => { + println("TWO") + "two" + } + case _ => { + // Nice + "other" + } + } + +} +>>> +object a { + + val x: Int => String = { + case 2 => + println("TWO") + "two" + case _ => + // Nice + "other" + } + +} +<<< case clauses to unit +object a { + + val x: Int => Unit = { + case 0 => + case 1 => {} + case 2 => { + } + case _ => {println("?")} + } + +} +>>> +object a { + + val x: Int => Unit = { + case 0 => + case 1 => + case 2 => + case _ => println("?") + } + +} diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-if.stat b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-if.stat new file mode 100644 index 0000000000..4f5ded1d8e --- /dev/null +++ b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-if.stat @@ -0,0 +1,155 @@ +rewrite.rules = [RedundantBraces] +<<< if-else simple +object a { + + def x(i: Int): String = + if (i == 0) { + "zero" + } else if (i < 0) { + "sub-zero" + } else { + "other" + } + +} +>>> +object a { + + def x(i: Int): String = + if (i == 0) + "zero" + else if (i < 0) + "sub-zero" + else + "other" + +} +<<< if-else with stuff +object a { + + def x(i: Int): String = + if (i == 0) { + /* what */ + "zero" + } else if (i < 0) { + // Ahh + // Ahh2 + "sub-zero" + } else if (i > 9000) { + println("i = " + i) + "over 9000" + } else if (i > 90000) { + println("i = " + i) + "over 90000!" + } else if (i == 1) { + {{ "one" }} + } else { + "other" + } +} +>>> +object a { + + def x(i: Int): String = + if (i == 0) + /* what */ + "zero" + else if (i < 0) + // Ahh + // Ahh2 + "sub-zero" + else if (i > 9000) { + println("i = " + i) + "over 9000" + } else if (i > 90000) { + println("i = " + i) + "over 90000!" + } else if (i == 1) + "one" + else + "other" +} +<<< if without else +object a { + + val x: Int => Unit = + if (i == 0) { + println("zero") + } else if (i < 0) { + println("sub-zero") + } + + def y(i: Int) = + if (i > 0) { + println("yes!") + } + +} +>>> +object a { + + val x: Int => Unit = + if (i == 0) + println("zero") + else if (i < 0) + println("sub-zero") + + def y(i: Int) = + if (i > 0) + println("yes!") + +} +<<< nested if +object a { + def y(i: Int) = + if (i > 0) { + if (i < 100) { + println("yes!") + } + } else if (i < 0) { + if (i >= -100) { + println("NO!") + } + } +} +>>> +object a { + def y(i: Int) = + if (i > 0) { + if (i < 100) + println("yes!") + } else if (i < 0) + if (i >= -100) + println("NO!") +} +<<< nested if-else +object a { + def y(i: Int) = + if (i > 0) { + if (i < 100) { + println("yes!") + } else { + println("yes??") + } + } else if (i < 0) { + if (i >= -100) { + println("NO!") + } else { + println("no??") + } + } +} +>>> +object a { + def y(i: Int) = + if (i > 0) + if (i < 100) + println("yes!") + else + println("yes??") + else if (i < 0) + if (i >= -100) + println("NO!") + else + println("no??") +} diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-precedence.stat b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-precedence.stat new file mode 100644 index 0000000000..b56ba0c447 --- /dev/null +++ b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-precedence.stat @@ -0,0 +1,17 @@ +rewrite.rules = [RedundantBraces] +<<< infix operator precedence +{1 + 2} * 3 +>>> +{ 1 + 2 } * 3 +<<< infix operator precedence negative +1 + { 2 * 3 } +>>> +1 + 2 * 3 +<<< infix operator associativity +{a :: b} :: c +>>> +{ a :: b } :: c +<<< infix operator associativity negative +a :: {b :: c} +>>> +a :: b :: c diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat index d5dcc2433e..38556a2515 100644 --- a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat +++ b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat @@ -143,19 +143,13 @@ object a { 2 } } -<<< fix your own nested blox +<<< fixes your nested blocks object a { - def x: Int = { - { // comment - 2 - } // end - } + def x: Int = {{ // comment + 2 } } } >>> object a { - def x: Int = { - { // comment - 2 - } // end - } + def x: Int = // comment + 2 }