diff --git a/.gitignore b/.gitignore index e9269c4c..b02ca0f2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ docs/build nohup.out .history .cache +.idea/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..0b421590 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: scala +scala: + - 2.10.3 + - 2.9.3 +jdk: + - oraclejdk7 + - openjdk7 \ No newline at end of file diff --git a/README.rst b/README.rst index 7b735c0f..28989499 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,9 @@ Scalariform =========== +.. image:: https://travis-ci.org/daniel-trinh/scalariform.png?branch=master + :target: https://travis-ci.org/daniel-trinh/scalariform + Scalariform is a code formatter for Scala. It's available as a library, a stand-alone command line tool, or via integrations with various editors and build tools (listed below). @@ -14,6 +17,13 @@ Scalariform is licenced under `The MIT Licence`_. .. _Scala Style Guide: http://davetron5000.github.com/scala-style/ .. _The MIT Licence: http://www.opensource.org/licenses/mit-license.php +Integration with sbt +-------------------- + +A version for sbt >= 0.13.x has been written by Peter Vlugter: https://github.com/daniel-trinh/sbt-scalariform + +Please see https://github.com/sbt/sbt-scalariform for older versions of sbt. + Integration with Eclipse ------------------------ @@ -22,7 +32,7 @@ Scala IDE for Eclipse uses Scalariform for code formatting: - Right click in the editor -> Source -> Format - Press Ctrl-Shift-F -If you select some lines, only those will be formatted. +If you select some lines, only those will be formatted. You can also configure formatting to be run as a save action (Window -> Preferences -> Java -> Editor -> Save Actions). @@ -31,7 +41,7 @@ To set preferences, go to Window -> Preferences -> Scala -> Formatter Integration with Emacs/ENSIME ----------------------------- -"`ENSIME`_ uses the Scalariform library to format Scala sources. Type C-c C-v f to format the current buffer." +"`ENSIME`_ uses the Scalariform library to format Scala sources. Type C-c C-v f to format the current buffer." http://aemon.com/file_dump/ensime_manual.html#tth_sEc4.8 @@ -73,16 +83,6 @@ Usage:: -Integration with sbt --------------------- - -`sbt-scalariform`_, written by Olivier Michallat, provides an sbt plugin contributing formatting actions for sbt 0.7.x. - -A version for sbt 0.10.x has been written by Peter Vlugter: https://github.com/typesafehub/sbt-scalariform - -.. _sbt-scalariform: http://github.com/olim7t/sbt-scalariform - - Integration with TextMate ------------------------- @@ -123,26 +123,72 @@ alignParameters Default: ``false`` -Align class/function parameters in the same column. For example, if ``false``, then:: +Align class/function parameters (modifiers and name, type, and defaults) in three columns. + +For example, if ``false``, then:: class Person(name: String, - age: Int, + age: Int = 24, birthdate: Date, - astrologicalSign: String, + astrologicalSign: String = "libra", shoeSize: Int, favoriteColor: java.awt.Color) If ``true``, then:: - class Person(name: String, - age: Int, - birthdate: Date, - astrologicalSign: String, - shoeSize: Int, - favoriteColor: java.awt.Color) + class Person(name: String, + age: Int = 24, + birthdate: Date, + astrologicalSign: String = "libra", + shoeSize: Int, + favoriteColor: java.awt.Color) + +This will also place the "implicit" keyword in parameters on it's own line, whenever +the parameter being formatted contains a newline:: + +For example, if ``false``, then:: + + def formatBirthDate( + implicit birthdate: Date = Date("11/11/11"), + birthtime: Time): DateTime + +If ``true``, then:: + + def formatBirthDate( + implicit + birthdate: Date = Date("11/11/11"), + birthtime: Time): DateTime This option is disabled if ``indentWithTabs`` is ``true``. + +alignArguments +~~~~~~~~~~~~~~ + +Default: ``false`` + +Aligns mult-line arguments + +For example, if ``false``, then:: + + Cake(candles = 10, + frostingFlavor = Vanilla, + layerFlavor = Chocolate, + icecream = true + ) + +If ``true``, then:: + + Cake( + candles = 10, + frostingFlavor = Vanilla, + layerFlavor = Chocolate, + icecream = true + ) + +This option is disabled if ``indentWithTabs`` is ``true``. + + alignSingleLineCaseStatements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -204,7 +250,7 @@ using `Compact Control Readability`_ style: try { foo() - } + } catch { case _ => bar() } @@ -319,7 +365,7 @@ indentSpaces Default: ``2`` -The number of spaces to use for each level of indentation. +The number of spaces to use for each level of indentation. This option is ignored if ``indentWithTabs`` is ``true``. @@ -330,7 +376,7 @@ Default: ``false`` Use a tab for each level of indentation. When set to ``true``, this ignores any setting given for ``indentSpaces``. In addition, for the -moment, ``alignSingleLineCaseStatements`` and ``alignParameters`` +moment, ``alignSingleLineCaseStatements``, ``alignArguments``, and ``alignParameters`` options are not supported when indenting with tabs, and XML indentation is handled differently. @@ -341,14 +387,14 @@ Default: ``false`` If ``true``, start a multi-line Scaladoc comment body on same line as the opening comment delimiter:: - /** This method applies f to each + /** This method applies f to each * element of the given list. */ If ``false``, start the comment body on a separate line below the opening delimiter:: - /** - * This method applies f to each + /** + * This method applies f to each * element of the given list. */ @@ -358,7 +404,7 @@ preserveDanglingCloseParenthesis Default: ``false`` If ``true``, it will keep a newline before a close parenthesis ')' in an -argument expression. For example:: +argument expression or parameter clause. For example:: val book = Book( name = "Name", @@ -373,6 +419,21 @@ If ``false``, the parenthesis will be joined to the end of the argument list:: author = "Author", rating = 5) + +Or with parameters, if ``true``:: + + def findBooks( + author: Option[String] = None, + title: Option[String] = None + ): List[Book] + +If ``false``:: + + def findBooks( + author: Option[String] = None, + title: Option[String] = None): List[Book] + + placeScaladocAsterisksBeneathSecondAsterisk ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -381,14 +442,14 @@ Default: ``false`` If ``true``, Scaladoc asterisks will be placed beneath the second asterisk:: /** Wibble - * wobble + * wobble */ class A Otherwise, if ``false``, beneath the first asterisk:: /** Wibble - * wobble + * wobble */ class A @@ -474,6 +535,18 @@ If ``false``,:: case elem@Multi(values@_*) => +spacesAroundMultiImports +~~~~~~~~~~~~~~~~~~~~~~~~ + +Default: ``true`` + +Whether or not to add spaces around mutli-imports. For example, If ``true``, then:: + + import a.{ b, c, d } + +If ``false``, then:: + + import a.{b,c,d} Scala Style Guide ~~~~~~~~~~~~~~~~~ @@ -487,14 +560,14 @@ make uncompliant source more compliant. ============================ ========= ========= Preference Value Default? ============================ ========= ========= -alignParameters ``false`` -compactStringConcatenation ``false`` +alignParameters ``false`` +compactStringConcatenation ``false`` doubleIndentClassDeclaration ``true`` No -indentSpaces ``2`` +indentSpaces ``2`` placeScaladocAsterisksBeneathSecondAsterisk ``true`` No -preserveSpaceBeforeArguments ``false`` -rewriteArrowSymbols ``false`` -spaceBeforeColon ``false`` +preserveSpaceBeforeArguments ``false`` +rewriteArrowSymbols ``false`` +spaceBeforeColon ``false`` spaceInsideBrackets ``false`` spaceInsideParentheses ``false`` ============================ ========= ========= @@ -510,24 +583,24 @@ format: [ON|OFF] Disables the formatter for selective portions of a source file:: // format: OFF <-- this directive disables formatting from this point - class AsciiDSL { + class AsciiDSL { n ¦- "1" -+ { n: Node => - n ¦- "i" - n ¦- "ii" - n ¦- "iii" - n ¦- "iv" + n ¦- "i" + n ¦- "ii" + n ¦- "iii" + n ¦- "iv" n ¦- "v" } n ¦- "2" n ¦- "3" -+ { n: Node => - n ¦- "i" + n ¦- "i" n ¦- "ii" -+ { n: Node => n ¦- "a" n ¦- "b" n ¦- "c" } - n ¦- "iii" - n ¦- "iv" + n ¦- "iii" + n ¦- "iv" n ¦- "v" } // format: ON <-- formatter resumes from this point diff --git a/cli/src/main/scala/scalariform/commandline/Main.scala b/cli/src/main/scala/scalariform/commandline/Main.scala index 3cd343b6..9dea118d 100644 --- a/cli/src/main/scala/scalariform/commandline/Main.scala +++ b/cli/src/main/scala/scalariform/commandline/Main.scala @@ -13,7 +13,7 @@ import scalariform.ScalaVersions object Main { def main(args: Array[String]) { - exit(if (process(args)) 1 else 0) + sys.exit(if (process(args)) 1 else 0) } def process(args: Array[String]): Boolean = { diff --git a/misc/src/main/scala/scalariform/corpusscan/CorpusScanner.scala b/misc/src/main/scala/scalariform/corpusscan/CorpusScanner.scala index 143b3deb..bd0e8045 100644 --- a/misc/src/main/scala/scalariform/corpusscan/CorpusScanner.scala +++ b/misc/src/main/scala/scalariform/corpusscan/CorpusScanner.scala @@ -67,11 +67,15 @@ object CorpusScanner extends SpecificFormatter { } -object Runner extends App { +object Runner { val corpusDir = "/home/matthew/coding/scala-corpus/repos2" // val corpusDir = "/home/matt/scala-corpus" + def main(args: Array[String]) { + formatInPlace() + } + def checkParser() { val files = ScalaFileWalker.findScalaFiles(corpusDir) var count = 0 @@ -101,7 +105,4 @@ object Runner extends App { } println(count + " files formatted.") } - - formatInPlace() - -} +} \ No newline at end of file diff --git a/misc/src/main/scala/scalariform/gui/FormatterFrame.scala b/misc/src/main/scala/scalariform/gui/FormatterFrame.scala index f12d1c27..c5a760a9 100644 --- a/misc/src/main/scala/scalariform/gui/FormatterFrame.scala +++ b/misc/src/main/scala/scalariform/gui/FormatterFrame.scala @@ -426,4 +426,3 @@ object Samples { |}""".stripMargin } // format: ON - diff --git a/misc/src/main/scala/scalariform/gui/Main.scala b/misc/src/main/scala/scalariform/gui/Main.scala index 35599e9c..1da7b728 100644 --- a/misc/src/main/scala/scalariform/gui/Main.scala +++ b/misc/src/main/scala/scalariform/gui/Main.scala @@ -3,14 +3,14 @@ package scalariform.gui import scalariform.utils.Utils._ import javax.swing.JFrame -object Main extends App { +object Main { - onSwingThread { - val frame = new FormatterFrame - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) - frame.setSize(1280, 600) - frame.setVisible(true) + def main(args: Array[String]) { + onSwingThread { + val frame = new FormatterFrame + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) + frame.setSize(1280, 600) + frame.setVisible(true) + } } - -} - +} \ No newline at end of file diff --git a/misc/src/main/scala/scalariform/gui/ParseTreeModel.scala b/misc/src/main/scala/scalariform/gui/ParseTreeModel.scala index edc26d7d..5fad7939 100644 --- a/misc/src/main/scala/scalariform/gui/ParseTreeModel.scala +++ b/misc/src/main/scala/scalariform/gui/ParseTreeModel.scala @@ -31,7 +31,7 @@ class ParseTreeModel(rootAstNode: AstNode) extends TreeModel { val fields = astNode.getFields lazy val children = fields flatMap { - case (_, None) | (_, Nil) ⇒ None + case (_, None) | (_, Nil) ⇒ None case (fieldName, value) ⇒ Some(makeTreeNode(value.asInstanceOf[AnyRef], fieldName)) } diff --git a/misc/src/main/scala/scalariform/gui/SwingUtils.scala b/misc/src/main/scala/scalariform/gui/SwingUtils.scala index 7b5180d2..37a6d2db 100644 --- a/misc/src/main/scala/scalariform/gui/SwingUtils.scala +++ b/misc/src/main/scala/scalariform/gui/SwingUtils.scala @@ -4,10 +4,9 @@ import javax.swing.event.ListSelectionListener import javax.swing.event.ListSelectionEvent object SwingUtils { - + implicit def fn2ListSelectionListener(handler: ListSelectionEvent ⇒ Unit): ListSelectionListener = new ListSelectionListener() { def valueChanged(e: ListSelectionEvent) = handler(e) } - } \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index b84ef8d1..adef0880 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -8,16 +8,30 @@ import com.typesafe.sbt.SbtScalariform.ScalariformKeys import scalariform.formatter.preferences._ object ScalariformBuild extends Build { + + // This is to make sure nobody tries to compile with 1.6 as the target JDK. + // Not clear if this will actually work on 1.8, needs to be tested when that is out. + val specVersion = sys.props("java.specification.version") + val mismatchedSpecificationMessage = + """|Java 1.7 is required for building Scalariform. + | + |This is due to a dependency on the javax.swing library, which + |had an API change from 1.6 to 1.7. + | + |Using 1.7 to build requires setting SBT to use JDK 1.7 or higher -- if SBT is + |booting on JDK 1.6, you will get a javax.swing related compilation error.""".stripMargin + assert(specVersion == "1.7", mismatchedSpecificationMessage) lazy val commonSettings = Defaults.defaultSettings ++ SbtScalariform.defaultScalariformSettings ++ Seq( - organization := "org.scalariform", + organization := "com.danieltrinh", version := "0.1.5-SNAPSHOT", - scalaVersion := "2.10.0", + scalaVersion := "2.10.3", crossScalaVersions := Seq( - // "2.11.0-M2", + "2.11.0-M8", + "2.11.0-M7", "2.10.0", "2.10.1", - "2.9.3", "2.9.2", "2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0", - "2.8.2", "2.8.1", "2.8.0"), + "2.9.3", "2.9.2", "2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0" + ), exportJars := true, // Needed for cli oneJar retrieveManaged := true, scalacOptions += "-deprecation", @@ -34,18 +48,33 @@ object ScalariformBuild extends Build { publish := (), publishLocal := ())) aggregate (scalariform, cli, misc) + implicit class Regex(sc: StringContext) { + def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*) + } + def getScalaTestDependency(scalaVersion: String) = scalaVersion match { - case "2.8.0" ⇒ "org.scalatest" %% "scalatest" % "1.3.1.RC2" % "test" - case "2.10.0" ⇒ "org.scalatest" %% "scalatest" % "1.9.1" % "test" - case "2.10.1" ⇒ "org.scalatest" %% "scalatest" % "1.9.1" % "test" - case "2.9.3" ⇒ "org.scalatest" %% "scalatest" % "1.9.1" % "test" - case _ ⇒ "org.scalatest" %% "scalatest" % "1.7.2" % "test" + case "2.11.0-M8" ⇒ "org.scalatest" %% s"scalatest" % "2.1.RC1" % "test" + case "2.11.0-M7" ⇒ "org.scalatest" %% s"scalatest" % "2.0.1-SNAP4" % "test" + case r"2.10.\d+" ⇒ "org.scalatest" % "scalatest_2.10" % "2.0" % "test" + case "2.9.3" ⇒ "org.scalatest" %% "scalatest" % "1.9.1" % "test" + case _ ⇒ "org.scalatest" %% "scalatest" % "1.7.2" % "test" + } + + def get2_11Dependencies(scalaVersion: String): List[ModuleID] = scalaVersion match { + case r"2.11.0-M\d" => List( + "org.scala-lang.modules" %% "scala-xml" % "1.0.0-RC7", + "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.0-RC5" + ) + case _ => Nil } lazy val scalariform: Project = Project("scalariform", file("scalariform"), settings = subprojectSettings ++ sbtbuildinfo.Plugin.buildInfoSettings ++ eclipseSettings ++ Seq( - libraryDependencies <<= (scalaVersion, libraryDependencies) { (sv, deps) ⇒ deps :+ getScalaTestDependency(sv) }, + libraryDependencies <<= (scalaVersion, libraryDependencies) { (sv, deps) ⇒ + deps ++ get2_11Dependencies(sv) :+ getScalaTestDependency(sv) + }, + testOptions in Test += Tests.Argument("-oI"), pomExtra := pomExtraXml, publishMavenStyle := true, publishArtifact in Test := false, @@ -57,8 +86,10 @@ object ScalariformBuild extends Build { publishTo <<= isSnapshot(getPublishToRepo))) def getPublishToRepo(isSnapshot: Boolean) = - if (isSnapshot) Some("snapshots" at "https://oss.sonatype.org/content/repositories/snapshots") - else Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2") + if (isSnapshot) + Some("snapshots" at "https://oss.sonatype.org/content/repositories/snapshots") + else + Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2") lazy val cli = Project("cli", file("cli"), settings = subprojectSettings ++ SbtOneJar.oneJarSettings ++ Seq( @@ -88,8 +119,8 @@ object ScalariformBuild extends Build { - git@github.com:mdr/scalariform.git - scm:git:git@github.com:mdr/scalariform + git@github.com:daniel-trinh/scalariform.git + scm:git:git@github.com:daniel-trinh/scalariform @@ -97,6 +128,11 @@ object ScalariformBuild extends Build { Matt Russell https://github.com/mdr/ + + daniel-trinh + Daniel Trinh + https://github.com/daniel-trinh/ + -} +} \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index 9b860e23..37b489cb 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.12.3 +sbt.version=0.13.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 4a817d0f..fcd57349 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,18 +1,15 @@ -resolvers += Classpaths.typesafeResolver +resolvers += Classpaths.typesafeReleases addSbtPlugin("com.github.retronym" % "sbt-onejar" % "0.8") resolvers += Classpaths.typesafeSnapshots - -addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.0.1") +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0") -resolvers += Resolver.url("sbt-plugin-releases", new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases/"))(Resolver.ivyStylePatterns) +addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.2.1") -addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8") +addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.1") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.2.2") - -retrieveManaged := true +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.3.0") +retrieveManaged := true \ No newline at end of file diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt index 4393611d..21f94b2c 100644 --- a/project/project/plugins.sbt +++ b/project/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += Classpaths.typesafeSnapshots -addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.2") +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0") diff --git a/scalariform/src/main/scala/scalariform/formatter/Alignment.scala b/scalariform/src/main/scala/scalariform/formatter/Alignment.scala new file mode 100644 index 00000000..626e8d8c --- /dev/null +++ b/scalariform/src/main/scala/scalariform/formatter/Alignment.scala @@ -0,0 +1,59 @@ +package scalariform.formatter + +import scalariform.parser._ +import Math._ +import scalariform.formatter.preferences._ + +// For now, this is just a place to store alignment related functionality. +// TOOD: refactor duplicate behavior in here +object Alignment { + type EitherAlignableParam = Either[ConsecutiveSingleLineParams, Param] + type EitherAlignableEqualsExpr = Either[ConsecutiveSingleLineEqualsExprs, CallExpr] + type EitherAlignableCaseClause = Either[ConsecutiveSingleLineCaseClauses, CaseClause] + + case class ConsecutiveSingleLineParams(params: List[Param], maxSectionLengths: ParamSectionLengths, thisSectionLengths: ParamSectionLengths) { + def prepend(param: Param, newLengths: ParamSectionLengths): ConsecutiveSingleLineParams = { + ConsecutiveSingleLineParams(param :: params, maxSectionLengths.max(newLengths), thisSectionLengths) + } + + // Splits the head param off, returning it and the remaining params. + // This doesn't recalculate section lengths. + def pop: (Option[Param], ConsecutiveSingleLineParams) = params match { + case param :: remainingParams ⇒ + (Some(param), copy(params = remainingParams)) + case Nil ⇒ + (None, copy(params = Nil)) + } + } + + case class ConsecutiveSingleLineEqualsExprs( + equalsExprs: List[EqualsExpr], + largestIdLength: Int) { + + def prepend(equalsExpr: EqualsExpr, length: Int) = { + ConsecutiveSingleLineEqualsExprs(equalsExpr :: equalsExprs, max(length, largestIdLength)) + } + } + + case class ConsecutiveSingleLineCaseClauses( + clauses: List[CaseClause], + largestCasePatternLength: Int, + smallestCasePatternLength: Int) { + def prepend(clause: CaseClause, length: Int) = + ConsecutiveSingleLineCaseClauses(clause :: clauses, max(length, largestCasePatternLength), min(length, smallestCasePatternLength)) + + def patternLengthRange = largestCasePatternLength - smallestCasePatternLength + + } + + case class ParamSectionLengths(prefixLength: Int, idLength: Int, prefixAndIdLength: Int, typeLength: Int) { + def max(newParamSectionLength: ParamSectionLengths): ParamSectionLengths = { + val ParamSectionLengths(newPrefixLength, newIdLength, newPrefixAndIdLength, newTypeLength) = newParamSectionLength + ParamSectionLengths( + math.max(prefixLength, newPrefixLength), + math.max(idLength, newIdLength), + math.max(prefixAndIdLength, newPrefixAndIdLength), + math.max(typeLength, newTypeLength)) + } + } +} \ No newline at end of file diff --git a/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala b/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala index 9255a4bc..9235dd0a 100644 --- a/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala +++ b/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala @@ -7,6 +7,7 @@ import scalariform.utils.Utils import scalariform.utils.TextEditProcessor import scalariform.utils.BooleanLang._ import scalariform.formatter.preferences._ +import Alignment._ import PartialFunction._ import scala.math.{ max, min } @@ -61,28 +62,23 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi formatResult } - private def groupClauses(caseClausesAstNode: CaseClauses): List[Either[ConsecutiveSingleLineCaseClauses, CaseClause]] = { + private def groupClauses(caseClausesAstNode: CaseClauses): List[EitherAlignableCaseClause] = { val clausesAreMultiline = containsNewline(caseClausesAstNode) || hiddenPredecessors(caseClausesAstNode.firstToken).containsNewline - def groupClauses(caseClauses: List[CaseClause], first: Boolean): List[Either[ConsecutiveSingleLineCaseClauses, CaseClause]] = + def groupClauses(caseClauses: List[CaseClause], first: Boolean): List[EitherAlignableCaseClause] = caseClauses match { case Nil ⇒ Nil case (caseClause @ CaseClause(casePattern, statSeq)) :: otherClauses ⇒ val otherClausesGrouped = groupClauses(otherClauses, first = false) - val formattedCasePattern = { - val casePatternSource = getSource(casePattern) - val casePatternFormatResult = formatCasePattern(casePattern)(FormatterState(indentLevel = 0)) - val offset = casePattern.firstToken.offset - val edits = writeTokens(casePatternSource, casePattern.tokens, casePatternFormatResult, offset) - TextEditProcessor.runEdits(casePatternSource, edits) + val formattedCasePattern = formattedAstNode(casePattern) { + formatCasePattern(casePattern)(FormatterState(indentLevel = 0)) } val newlineBeforeClause = hiddenPredecessors(caseClause.firstToken).containsNewline || previousCaseClauseEndsWithNewline(caseClause, caseClausesAstNode) // To evaluate whether a clause body is multiline, we ignore a trailing newline: - val prunedStatSeq = pruneTrailingNewline(statSeq) val clauseBodyIsMultiline = containsNewline(pruneTrailingNewline(statSeq)) || statSeq.firstTokenOption.exists(hiddenPredecessors(_).containsNewline) @@ -102,14 +98,6 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi groupClauses(caseClausesAstNode.caseClauses, first = true) } - private case class ConsecutiveSingleLineCaseClauses(clauses: List[CaseClause], largestCasePatternLength: Int, smallestCasePatternLength: Int) { - def prepend(clause: CaseClause, length: Int) = - ConsecutiveSingleLineCaseClauses(clause :: clauses, max(length, largestCasePatternLength), min(length, smallestCasePatternLength)) - - def patternLengthRange = largestCasePatternLength - smallestCasePatternLength - - } - private def formatCasePattern(casePattern: CasePattern, arrowInstructionOpt: Option[PlaceAtColumn] = None)(implicit formatterState: FormatterState): FormatResult = { val CasePattern(caseToken: Token, pattern: Expr, guardOption: Option[Guard], arrow: Token) = casePattern var formatResult: FormatResult = NoFormatResult @@ -164,5 +152,4 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi case Some((separator, None)) if separator.isNewline ⇒ statSeq.copy(otherStats = statSeq.otherStats.init) case _ ⇒ statSeq } - -} +} \ No newline at end of file diff --git a/scalariform/src/main/scala/scalariform/formatter/CommentFormatter.scala b/scalariform/src/main/scala/scalariform/formatter/CommentFormatter.scala index 42ae48b0..ad49d759 100644 --- a/scalariform/src/main/scala/scalariform/formatter/CommentFormatter.scala +++ b/scalariform/src/main/scala/scalariform/formatter/CommentFormatter.scala @@ -14,7 +14,8 @@ trait CommentFormatter { self: HasFormattingPreferences with ScalaFormatter ⇒ val (contents, _) = rest.splitAt(rest.length - "*/".length) val firstLine :: otherLines = contents.split("""\r?\n([ \t]*(\*(?!/))?)?""", Integer.MAX_VALUE).toList val afterStarSpaces = if (formattingPreferences(MultilineScaladocCommentsStartOnFirstLine)) 2 else 1 - val adjustedLines = dropInitialSpaces(firstLine, 1) :: (otherLines map { dropInitialSpaces(_, afterStarSpaces) }) + val initialSpaces = firstLine takeWhile (_.isWhitespace) + val adjustedLines = dropInitialSpaces(firstLine, initialSpaces.size) :: (otherLines map { dropInitialSpaces(_, afterStarSpaces) }) // val adjustedLines map { line ⇒ if (line startsWith "*/") "*" + line else line } (start, adjustedLines) } diff --git a/scalariform/src/main/scala/scalariform/formatter/ExprFormatter.scala b/scalariform/src/main/scala/scalariform/formatter/ExprFormatter.scala old mode 100644 new mode 100755 index 4303b947..b7e55b2f --- a/scalariform/src/main/scala/scalariform/formatter/ExprFormatter.scala +++ b/scalariform/src/main/scala/scalariform/formatter/ExprFormatter.scala @@ -4,9 +4,10 @@ import scalariform.lexer.Chars import scalariform.lexer.Token import scalariform.lexer.Tokens._ import scalariform.parser._ -import scalariform.utils.Utils +import scalariform.utils.{ TextEditProcessor, Utils } import scalariform.formatter.preferences._ import scala.PartialFunction._ +import Alignment._ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter with HasHiddenTokenInfo with TypeFormatter with TemplateFormatter with ScalaFormatter with XmlFormatter with CaseClauseFormatter ⇒ @@ -114,8 +115,19 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi case (PrefixExprElement(_), _) ⇒ if (Chars.isOperatorPart(element.firstToken.text(0))) CompactEnsuringGap else Compact case (Argument(_), _) ⇒ Compact case (_, _: ArgumentExprs) if formattingPreferences(PreserveSpaceBeforeArguments) ⇒ CompactPreservingGap // TODO: Probably not needed now with CallExpr - case (_, _) if element.firstTokenOption exists { firstToken ⇒ newlineBefore(firstToken) && !(Set(COMMA, COLON) contains firstToken.tokenType) } ⇒ - currentFormatterState = currentFormatterState.indentForExpressionBreakIfNeeded + case (_, elem) if element.firstTokenOption exists { firstToken ⇒ newlineBefore(firstToken) && !(Set(COMMA, COLON) contains firstToken.tokenType) } ⇒ + val isNestedArgument = elem match { + case Argument(Expr(CallExpr(_, _, _, moreArgs, _) :: tail)) if moreArgs.isEmpty ⇒ false + case Argument(Expr(EqualsExpr(_, _, Expr(headExpr :: innerTail)) :: tail)) ⇒ headExpr match { + case CallExpr(_, _, _, moreArgs, _) ⇒ if (moreArgs.isEmpty) false else true + case _ ⇒ false + } + case _ ⇒ true + } + + if (isNestedArgument) + currentFormatterState = currentFormatterState.indentForExpressionBreakIfNeeded + currentFormatterState.currentIndentLevelInstruction } @@ -273,17 +285,127 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi def format(argumentExprs: ArgumentExprs)(implicit formatterState: FormatterState): (FormatResult, FormatterState) = argumentExprs match { case BlockArgumentExprs(contents) ⇒ (format(contents), formatterState) - case ParenArgumentExprs(lparen, contents, rparen) ⇒ + case args @ ParenArgumentExprs(lparen, contents, rparen) ⇒ var currentFormatterState = formatterState var formatResult: FormatResult = NoFormatResult - val (contentsFormatResult, updatedFormatterState) = formatExprElements(GeneralTokens(List(lparen)) :: contents) + + val (contentsFormatResult, updatedFormatterState) = formatExprElements(GeneralTokens(List(lparen)) :: contents)(currentFormatterState) formatResult ++= contentsFormatResult currentFormatterState = updatedFormatterState - if (formattingPreferences(PreserveDanglingCloseParenthesis) && hiddenPredecessors(rparen).containsNewline && contents.nonEmpty) + + val alignedFormatResult = alignArguments(args) + + formatResult ++= alignedFormatResult + val firstTokenIsOnNewline = contents.headOption.exists { x ⇒ + val firstToken = x.firstToken + hiddenPredecessors(firstToken).containsNewline || formatResult.tokenWillHaveNewline(firstToken) + } + if (firstTokenIsOnNewline) formatResult = formatResult.before(rparen, formatterState.currentIndentLevelInstruction) + (formatResult, currentFormatterState) } + def calculateEqualsExprIdLength(equalsExpr: EqualsExpr): Int = { + val maybeLength = condOpt(equalsExpr.lhs) { + case List(callExpr: CallExpr) ⇒ callExpr.id.length + } + maybeLength.getOrElse(0) + } + + protected def alignArguments(parenArguments: ParenArgumentExprs)(implicit formatterState: FormatterState): FormatResult = { + val alignArgsEnabled = formattingPreferences(AlignArguments) && !formattingPreferences(IndentWithTabs) + var formatResult: FormatResult = NoFormatResult + + var argumentFormatterState = formatterState + val ParenArgumentExprs(lparen, contents, rparen) = parenArguments + + /* Force a newline for the first argument if this is a set of + * multi line arguments: + * Call(aa, + * bb, + * cc) ==> + * Call( + * aa, + * bb, + * cc) + */ + val firstTwoArguments = contents.iterator.filter { x ⇒ + cond(x) { + case Argument(_) ⇒ true + case callExpr: CallExpr ⇒ true + case equalsExpr: EqualsExpr ⇒ true + } + }.take(2).toList + if (firstTwoArguments.size == 2) { + val secondArgument = firstTwoArguments.tail.head + val firstArgument = firstTwoArguments.head + if (hiddenPredecessors(secondArgument.firstToken).containsNewline) { + formatResult = formatResult.before(firstArgument.firstToken, argumentFormatterState.nextIndentLevelInstruction) + } + } + + var currentExprNewlineCount = 0 + + // TODO: refactor this as well as "def groupParams" to share logic + val eitherAlignableArguments = contents.foldLeft(List[EitherAlignableEqualsExpr]()) { (existingExprs, exprElement) ⇒ + if (!alignArgsEnabled) { + val nextArg = condOpt(exprElement) { + case Argument(Expr(List(callExpr: CallExpr))) ⇒ + Right(callExpr) :: existingExprs + case Argument(Expr(List(equalsExpr: EqualsExpr))) ⇒ + Left(ConsecutiveSingleLineEqualsExprs(List(equalsExpr), calculateEqualsExprIdLength(equalsExpr))) :: existingExprs + } + nextArg.getOrElse(existingExprs) + } else { + exprElement match { + case Argument(Expr(List(equalsExpr: EqualsExpr))) ⇒ + currentExprNewlineCount = hiddenPredecessors(exprElement.firstToken).text.count(_ == '\n') + existingExprs match { + case Left(exprs @ ConsecutiveSingleLineEqualsExprs(_, length)) :: tail ⇒ + if (currentExprNewlineCount >= 2) + Left(ConsecutiveSingleLineEqualsExprs(List(equalsExpr), calculateEqualsExprIdLength(equalsExpr))) :: existingExprs + else + Left(exprs.prepend(equalsExpr, calculateEqualsExprIdLength(equalsExpr))) :: tail + case Right(y) :: tail ⇒ + Left(ConsecutiveSingleLineEqualsExprs(List(equalsExpr), calculateEqualsExprIdLength(equalsExpr))) :: existingExprs + case Nil ⇒ + Left(ConsecutiveSingleLineEqualsExprs(List(equalsExpr), calculateEqualsExprIdLength(equalsExpr))) :: existingExprs + } + case Argument(Expr(List(callExpr: CallExpr))) ⇒ + currentExprNewlineCount = hiddenPredecessors(exprElement.firstToken).text.count(_ == '\n') + Right(callExpr) :: existingExprs + case _ ⇒ existingExprs + } + } + } + + eitherAlignableArguments foreach { + case Left(ConsecutiveSingleLineEqualsExprs(exprs, maxIdLength)) ⇒ + exprs foreach { expr ⇒ + val firstToken = expr.firstToken + + // If there's a newline before this argument, OR there will be a newline after formatting + if (hiddenPredecessors(firstToken).containsNewline || formatResult.tokenWillHaveNewline(firstToken)) { + formatResult = formatResult.before(firstToken, argumentFormatterState.nextIndentLevelInstruction) + formatResult = formatResult.before( + expr.equals, + PlaceAtColumn( + 0, + maxIdLength + 1, + Some(firstToken))) + } + } + case Right(callExpr) ⇒ + val firstToken = callExpr.firstToken + if (hiddenPredecessors(firstToken).containsNewline) { + formatResult = formatResult.before(firstToken, argumentFormatterState.nextIndentLevelInstruction) + } + } + + formatResult + } + private def format(parenExpr: ParenExpr)(implicit formatterState: FormatterState): (FormatResult, FormatterState) = { val ParenExpr(lparen, contents, rparen) = parenExpr format(ParenArgumentExprs(lparen, contents, rparen)) @@ -849,10 +971,159 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi }.foldLeft(formatResult._1 ++ result) { _ ++ _ } val currentLeftFormat = Some(EnsureNewlineAndIndent(0, paramClause.firstTokenOption)) formattingPreferences.indentStyle - (resultWithNewLines, formatResult._2, currentLeftFormat) + (resultWithNewLines, formatterState, currentLeftFormat) }._1 } + // TODO: Parts of this function might be useful in implementing other alignment features + protected def calculateParamSectionLengths(param: Param, first: Boolean)(implicit formatterState: FormatterState): Option[ParamSectionLengths] = { + val Param(annotations, modifiers, valOrVarOpt, id, paramTypeOpt, defaultValueOpt) = param + + val formattedParam = formattedAstNode(param) { + format(param)(formatterState) + } + + def calculateLengths: ParamSectionLengths = { + def calculatePrefixLength: Int = { + // Calculate longest "prefix" length. Annotations, modifiers, and val/var contribute + // to this number. + val allPrefixTokens = + annotations.flatMap(_.tokens) ++ + modifiers.flatMap(_.tokens) ++ + valOrVarOpt + var prefixLength = 0 + allPrefixTokens.filter(!_.isNewline).foreach { + prefixLength += _.length + } + + // Account for whitespace between prefix token types. This assumes everything + // will be placed on a single line with no newlines between prefixes. + val numberOfPrefixTypes = Seq( + !annotations.isEmpty, + valOrVarOpt.isDefined).count(_ == true) + modifiers.length + if (numberOfPrefixTypes > 0) + prefixLength += numberOfPrefixTypes - 1 + + prefixLength + } + + // Account for the colon (and possible space) after the parameter's name + def calculateIdLength: Int = { + if (formattingPreferences(SpaceBeforeColon)) + id.length + 2 + else + id.length + 1 + } + + def calculateTypeLength: Int = { + // Calculate longest "type" length. + val typeLengthOpt = paramTypeOpt map { + case (_, typeAst) ⇒ + val formattedType = formattedAstNode(typeAst) { + format(typeAst)(formatterState) + } + formattedType.length + } + typeLengthOpt.getOrElse(0) + } + + val prefixLength = calculatePrefixLength + val idLength = calculateIdLength + val prefixAndIdLength = { + if (prefixLength > 0) + prefixLength + idLength + 1 // the + 1 is to account for whitespace + else + idLength + } + + ParamSectionLengths(prefixLength, idLength, prefixAndIdLength, calculateTypeLength) + } + + val newlineBeforeParam = hiddenPredecessors(param.firstToken).containsNewline + + if (formattedParam.contains('\n') || (!first && !newlineBeforeParam)) + None + else + Some(calculateLengths) + } + + /** + * Groups consecutive single line params in a [[scalariform.parser.ParamClause]] for alignment. + * The head of the return value (and head of the params list in the returned ConsecutiveSingleLineParams is guaranteed + * to be the very first parameter in the paramClause. The other parameters are not necessarily in the order they appear. + * @param paramClause + * @param formatterState + * + * @return List of grouped params. Left stores a group of parameters that can be aligned together, + * Right stores an unalignable param. + */ + private def groupParams(paramClause: ParamClause, alignParameters: Boolean)(implicit formatterState: FormatterState): List[EitherAlignableParam] = { + val ParamClause(_, implicitOption, firstParamOption, otherParamsWithComma, _) = paramClause + + val otherParams = otherParamsWithComma.map { case (comma, param) ⇒ param } + + // This is reversed because "appendParamToGroup" works on lists, and will + // create the list in the reverse order of the list it is given. + val allParams = (firstParamOption.toList ++ otherParams).reverse + + def appendParamToGroup(previousParam: Option[Param], + paramToAppend: Param, + nextParam: Option[Param], + groupedParams: List[EitherAlignableParam]): List[EitherAlignableParam] = { + + // This unintuitive line is dependent on the ordering of groupedParams being passed + // in. It's in reverse. + val isFirstParam = !nextParam.isDefined + + val firstParamAlignable = !implicitOption.isDefined || + (newlineBefore(implicitOption.get) && otherParams != Nil && newlineBefore(otherParams.head)) + + val paramIsAlignable = alignParameters && (!isFirstParam || firstParamAlignable) + + if (alignParameters && paramIsAlignable) { + calculateParamSectionLengths(paramToAppend, isFirstParam) match { + case Some(sectionLengths) ⇒ + groupedParams match { + case Right(param) :: tail ⇒ + Left(ConsecutiveSingleLineParams(List(paramToAppend), sectionLengths, sectionLengths)) :: groupedParams + case Left(existingParams) :: tail ⇒ + if (previousParam.isDefined) { + /* Group params separately if a blank line between two params: + * case class Spacing(a: Int = 1, + * bee: Int = 2, + * + * ceee: String = "", + * deeee: Any = Nothing) + */ + val numNewlinesBeforeParam = hiddenPredecessors(previousParam.get.firstToken).text.count(_ == '\n') + if (numNewlinesBeforeParam >= 2) + Left(ConsecutiveSingleLineParams(List(paramToAppend), sectionLengths, sectionLengths)) :: groupedParams + else + Left(existingParams.prepend(paramToAppend, sectionLengths)) :: tail + } else { + Left(existingParams.prepend(paramToAppend, sectionLengths)) :: tail + } + case Nil ⇒ + Left(ConsecutiveSingleLineParams(List(paramToAppend), sectionLengths, sectionLengths)) :: Nil + } + case None ⇒ + Right(paramToAppend) :: groupedParams + } + } else { + Right(paramToAppend) :: groupedParams + } + } + + val staggeredParams = Utils.withPreviousAndNext(allParams) + + val paramsGroup = staggeredParams.foldLeft(List[EitherAlignableParam]()) { (groupedParams, prevAndNext) ⇒ + val (prevParam, param, nextParam) = prevAndNext + appendParamToGroup(prevParam, param, nextParam, groupedParams) + } + + paramsGroup + } + private def formatParamClause(paramClause: ParamClause, doubleIndentParams: Boolean = false)(implicit formatterState: FormatterState): (FormatResult, FormatterState) = { val ParamClause(lparen, implicitOption, firstParamOption, otherParams, rparen) = paramClause val paramIndent = if (doubleIndentParams) 2 else 1 @@ -860,36 +1131,159 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi var formatResult: FormatResult = NoFormatResult var paramFormatterState = formatterState val alignParameters = formattingPreferences(AlignParameters) && !formattingPreferences(IndentWithTabs) - for (firstParam ← firstParamOption) { - val token = implicitOption getOrElse firstParam.firstToken - if (hiddenPredecessors(token).containsNewline) { - formatResult = formatResult.before(token, formatterState.indent(paramIndent).currentIndentLevelInstruction) - paramFormatterState = if (alignParameters) formatterState.alignWithToken(relativeToken) else formatterState.indent(paramIndent) - } else if (containsNewline(firstParam) && alignParameters) - paramFormatterState = formatterState.alignWithToken(relativeToken) + + /* Force a newline for the first argument if this is a set of + * multi line arguments: + * def a(aa: Int, + * bb: String, + * cc: Boolean) ==> + * def a( + * aa: Int, + * bb: String, + * cc: Boolean) + */ + val firstTwoArguments = firstParamOption ++ otherParams.headOption.map(_._2) + if (firstTwoArguments.size == 2) { + val secondArgument = firstTwoArguments.tail.head + val firstArgument = firstTwoArguments.head + if (hiddenPredecessors(secondArgument.firstToken).containsNewline) { + formatResult ++= formatResult.before(firstArgument.firstToken, paramFormatterState.nextIndentLevelInstruction) + } + } + + val hasContent = implicitOption.isDefined || firstParamOption.isDefined || !otherParams.isEmpty + val firstTokenIsOnNewline = hiddenPredecessors(relativeToken).containsNewline || formatResult.tokenWillHaveNewline(relativeToken) + + // Place rparen on it's own line if this is a multi-line param clause + if (firstTokenIsOnNewline && hasContent) + formatResult = formatResult.before(rparen, paramFormatterState.currentIndentLevelInstruction) + + val groupedParams = groupParams(paramClause, alignParameters) + + // Separate the first parameter from the groupedParams, since we have to + // do special formatting for the first param. + val ((firstGroupedParamOption, maxSectionLengthsOption), otherGroupedParams) = groupedParams.headOption match { + case Some(x) ⇒ x match { + case Left(consecutiveParams) ⇒ + val (firstParam, remainingConsecutiveParams) = consecutiveParams.pop + ((firstParam, Some(remainingConsecutiveParams.maxSectionLengths)), Left(remainingConsecutiveParams) :: groupedParams.tail) + case p @ Right(param) ⇒ + ((Some(param), None), groupedParams.tail) + } + case None ⇒ ((None, None), Nil) + } + + for (firstParam ← firstGroupedParamOption) { + maxSectionLengthsOption match { + case Some(lengths) ⇒ + + // Indent prefixes (annotations, modifiers, and id) if alignParameters is enabled + alignFirstParam(firstParam) + + // Indent Type + indentType(firstParam, lengths) + + // Indent Default + indentDefault(firstParam, lengths) + case None ⇒ + alignFirstParam(firstParam) + } formatResult ++= format(firstParam)(paramFormatterState) } - for ((comma, param) ← otherParams) { - val token = param.firstToken - if (hiddenPredecessors(token).containsNewline) { + otherGroupedParams.foreach { + case Left(ConsecutiveSingleLineParams(params, maxSectionLengths, thisSectionLengths)) ⇒ + params.foreach { param ⇒ + val firstToken = param.firstToken + + // Indent prefixes (annotations, modifiers, and id) + alignOtherParams(firstToken) + + // Indent Type + indentType(param, maxSectionLengths) + + // Indent Default + indentDefault(param, maxSectionLengths) + + formatResult ++= format(param)(paramFormatterState) + } + case Right(param) ⇒ + alignOtherParams(param.firstToken) + formatResult ++= format(param)(paramFormatterState) + } + + def alignFirstParam(firstParam: Param) = { + // Place implicit on it's own line + for (implicitToken ← implicitOption) { + if (hiddenPredecessors(implicitToken).containsNewline || (containsNewline(firstParam) && alignParameters)) + formatResult = formatResult.before(implicitToken, paramFormatterState.indent(paramIndent).currentIndentLevelInstruction) + } + + val firstToken = firstParam.firstToken + val implicitOrFirstToken = implicitOption getOrElse firstToken + + if (alignParameters) + paramFormatterState = formatterState.alignWithToken(relativeToken) + + if (hiddenPredecessors(implicitOrFirstToken).containsNewline) { + formatResult = formatResult.before(firstToken, formatterState.indent(paramIndent).currentIndentLevelInstruction) + if (!alignParameters) + paramFormatterState = formatterState.indent(paramIndent) + } else if (containsNewline(firstParam) && alignParameters) { + paramFormatterState = formatterState.alignWithToken(relativeToken) + } + } + + def alignOtherParams(firstToken: Token) = { + if (hiddenPredecessors(firstToken).containsNewline) { paramFormatterState = if (alignParameters) formatterState.alignWithToken(relativeToken) else formatterState.indent(paramIndent) - formatResult = formatResult.before(token, paramFormatterState.currentIndentLevelInstruction) + formatResult = formatResult.before(firstToken, paramFormatterState.currentIndentLevelInstruction) + } + } + + def indentType(param: Param, maxSectionLengths: ParamSectionLengths) = { + for ((colon, typeAst) ← param.paramTypeOpt) { + val typeSpaces = maxSectionLengths.prefixAndIdLength + 1 + formatResult = formatResult.before( + typeAst.firstToken, + PlaceAtColumn( + 0, + typeSpaces, + paramFormatterState.indentRelativeToTokenOption)) + } + } + + def indentDefault(param: Param, maxSectionLengths: ParamSectionLengths) = { + for ((equal, default) ← param.defaultValueOpt) { + val defaultSpaces = { + maxSectionLengths.prefixAndIdLength + + maxSectionLengths.typeLength + 2 + } + formatResult = formatResult.before( + equal, + PlaceAtColumn( + 0, + defaultSpaces, + paramFormatterState.indentRelativeToTokenOption)) } - formatResult ++= format(param)(paramFormatterState) } + (formatResult, paramFormatterState) } private def format(param: Param)(implicit formatterState: FormatterState): FormatResult = { val Param(annotations: List[Annotation], modifiers: List[Modifier], valOrVarOpt: Option[Token], id: Token, paramTypeOpt: Option[(Token, Type)], defaultValueOpt: Option[(Token, Expr)]) = param var formatResult: FormatResult = NoFormatResult - for (annotation ← annotations) + + for (annotation ← annotations) { formatResult ++= format(annotation) - for ((colon, paramType) ← paramTypeOpt) + } + for ((colon, paramType) ← paramTypeOpt) { formatResult ++= format(paramType) - for ((equals, expr) ← defaultValueOpt) + } + for ((equals, expr) ← defaultValueOpt) { formatResult ++= format(expr) + } formatResult } @@ -916,9 +1310,13 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi val newFormatterState = formatterState.copy(inSingleLineBlock = singleLineBlock) if (singleLineBlock) { + if (!formattingPreferences(SpacesAroundMultiImports)) + formatResult = formatResult.before(firstImportSelector.firstToken, Compact) formatResult ++= format(firstImportSelector) for ((comma, otherImportSelector) ← otherImportSelectors) formatResult ++= format(otherImportSelector) + if (!formattingPreferences(SpacesAroundMultiImports)) + formatResult = formatResult.before(rbrace, Compact) } else { formatResult = formatResult.before(firstImportSelector.firstToken, formatterState.nextIndentLevelInstruction) formatResult ++= format(firstImportSelector) diff --git a/scalariform/src/main/scala/scalariform/formatter/FormatResult.scala b/scalariform/src/main/scala/scalariform/formatter/FormatResult.scala index 92d0cd54..406ebe64 100644 --- a/scalariform/src/main/scala/scalariform/formatter/FormatResult.scala +++ b/scalariform/src/main/scala/scalariform/formatter/FormatResult.scala @@ -6,9 +6,9 @@ import scalariform.parser._ import scalariform.utils._ object FormatResult { - + val EMPTY = FormatResult(Map(), Map(), Map()) - + } case class FormatResult(predecessorFormatting: Map[Token, IntertokenFormatInstruction], @@ -34,6 +34,13 @@ case class FormatResult(predecessorFormatting: Map[Token, IntertokenFormatInstru if (token.isNewline) formatNewline(token, formatInstruction) else before(token, formatInstruction) + def tokenWillHaveNewline(token: Token): Boolean = { + val hasNewlineInstruction = predecessorFormatting.get(token) map { + PartialFunction.cond(_) { case newlineInstruction: EnsureNewlineAndIndent ⇒ true } + } + hasNewlineInstruction.getOrElse(false) + } + def mergeWith(other: FormatResult): FormatResult = FormatResult(this.predecessorFormatting ++ other.predecessorFormatting, this.inferredNewlineFormatting ++ other.inferredNewlineFormatting, @@ -63,4 +70,4 @@ case object CompactPreservingGap extends IntertokenFormatInstruction case class EnsureNewlineAndIndent(indentLevel: Int, relativeTo: Option[Token] = None) extends IntertokenFormatInstruction /** Places the token at spaces number of spaces after the indent level, padding with spaces if necessary */ -case class PlaceAtColumn(indentLevel: Int, spaces: Int) extends IntertokenFormatInstruction \ No newline at end of file +case class PlaceAtColumn(indentLevel: Int, spaces: Int, relativeTo: Option[Token] = None) extends IntertokenFormatInstruction \ No newline at end of file diff --git a/scalariform/src/main/scala/scalariform/formatter/FormatterState.scala b/scalariform/src/main/scala/scalariform/formatter/FormatterState.scala index 63ae0e53..3f43c6e2 100644 --- a/scalariform/src/main/scala/scalariform/formatter/FormatterState.scala +++ b/scalariform/src/main/scala/scalariform/formatter/FormatterState.scala @@ -26,4 +26,4 @@ case class FormatterState( def clearExpressionBreakHappened = copy(expressionBreakHappened = false) -} +} \ No newline at end of file diff --git a/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala b/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala index 29cb2e0b..8337842d 100644 --- a/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala +++ b/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala @@ -45,6 +45,23 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte result } + /** + * Converts an AstNode into what it should look like in text after Scalariform has run. + * Useful for calculating the actual length of an [[scalariform.parser.AstNode]] after formatting. + * + * @param ast The AST to format and render as a string + * @param astFormatResult Should run formatting actions for 'ast' + * @return Formatted string representation of what the AstNode should look like after Scalariform + * has run + */ + protected def formattedAstNode(ast: AstNode)(astFormatResult: ⇒ FormatResult): String = { + val source = getSource(ast) + val formatResult = astFormatResult + val offset = ast.firstToken.offset + val edits = writeTokens(source, ast.tokens, formatResult, offset) + TextEditProcessor.runEdits(source, edits) + } + private def alterSuspendFormatting(text: String): Option[Boolean] = if (text contains "format: OFF") Some(true) @@ -162,11 +179,12 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte builder.append(" ") else writeIntertokenCompact() - case PlaceAtColumn(indentLevel, spaces) ⇒ + case PlaceAtColumn(indentLevel, spaces, relativeTo) ⇒ require(!formattingPreferences(IndentWithTabs)) writeIntertokenCompact() + val relativeIndent = relativeTo flatMap tokenIndentMap.get getOrElse 0 val indentLength = Spaces(formattingPreferences(IndentSpaces)).length(indentLevel) - builder.append(" " * (indentLength + spaces - builder.currentColumn)) + builder.append(" " * (indentLength + relativeIndent + spaces - builder.currentColumn)) case EnsureNewlineAndIndent(indentLevel, relativeTo) ⇒ require(!(formattingPreferences(IndentWithTabs) && relativeTo.isDefined)) val baseIndentOption = relativeTo flatMap tokenIndentMap.get diff --git a/scalariform/src/main/scala/scalariform/formatter/preferences/IFormattingPreferences.scala b/scalariform/src/main/scala/scalariform/formatter/preferences/IFormattingPreferences.scala index 18ab2c9f..6fa5a043 100644 --- a/scalariform/src/main/scala/scalariform/formatter/preferences/IFormattingPreferences.scala +++ b/scalariform/src/main/scala/scalariform/formatter/preferences/IFormattingPreferences.scala @@ -50,4 +50,4 @@ trait HasFormattingPreferences { val formattingPreferences: IFormattingPreferences -} +} \ No newline at end of file diff --git a/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala b/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala old mode 100644 new mode 100755 index 5ef1a32a..f693e55d --- a/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala +++ b/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala @@ -60,11 +60,12 @@ trait IntegerPreferenceDescriptor extends PreferenceDescriptor[Int] { } object AllPreferences { - val preferences: List[PreferenceDescriptor[_]] = List(RewriteArrowSymbols, IndentSpaces, SpaceBeforeColon, CompactStringConcatenation, - PreserveSpaceBeforeArguments, AlignParameters, DoubleIndentClassDeclaration, FormatXml, IndentPackageBlocks, + val preferences: List[PreferenceDescriptor[_]] = List( + RewriteArrowSymbols, IndentSpaces, SpaceBeforeColon, CompactStringConcatenation, + PreserveSpaceBeforeArguments, AlignParameters, AlignArguments, DoubleIndentClassDeclaration, FormatXml, IndentPackageBlocks, AlignSingleLineCaseStatements, AlignSingleLineCaseStatements.MaxArrowIndent, IndentLocalDefs, PreserveDanglingCloseParenthesis, SpaceInsideParentheses, SpaceInsideBrackets, SpacesWithinPatternBinders, MultilineScaladocCommentsStartOnFirstLine, IndentWithTabs, - CompactControlReadability, PlaceScaladocAsterisksBeneathSecondAsterisk, BreakMultipleParameterGroups, + CompactControlReadability, PlaceScaladocAsterisksBeneathSecondAsterisk, SpacesAroundMultiImports, BreakMultipleParameterGroups, BreakMultipleParameterGroups.BreakingThreshold) val preferencesByKey: Map[String, PreferenceDescriptor[_]] = { @@ -113,6 +114,12 @@ case object AlignParameters extends BooleanPreferenceDescriptor { val defaultValue = false } +case object AlignArguments extends BooleanPreferenceDescriptor { + val key = "alignArguments" + val description = "Align method arguments on different lines in the same column" + val defaultValue = false +} + case object DoubleIndentClassDeclaration extends BooleanPreferenceDescriptor { val key = "doubleIndentClassDeclaration" val description = "Double indent either a class's parameters or its inheritance list" @@ -151,6 +158,7 @@ case object IndentLocalDefs extends BooleanPreferenceDescriptor { val defaultValue = false } +@deprecated("This has been dropped in favor of always placing ')' on a newline if the clause is multi-line.", since = "0.1.5") case object PreserveDanglingCloseParenthesis extends BooleanPreferenceDescriptor { val key = "preserveDanglingCloseParenthesis" val description = "Allow a newline before a ')' in an argument expression" @@ -199,6 +207,12 @@ case object PlaceScaladocAsterisksBeneathSecondAsterisk extends BooleanPreferenc val defaultValue = false } +case object SpacesAroundMultiImports extends BooleanPreferenceDescriptor { + val key = "spacesAroundMultiImports" + val description = "Place spaces around multi imports (import a.{ b, c, d }" + val defaultValue = true +} + case object BreakMultipleParameterGroups extends BooleanPreferenceDescriptor { val key = "breakMultipleParametersGroups" val description = "Place newline after end of parameter group on multiple parameter group function definition" diff --git a/scalariform/src/main/scala/scalariform/lexer/Tokens.scala b/scalariform/src/main/scala/scalariform/lexer/Tokens.scala index 4e7b305a..b52fb7c8 100644 --- a/scalariform/src/main/scala/scalariform/lexer/Tokens.scala +++ b/scalariform/src/main/scala/scalariform/lexer/Tokens.scala @@ -114,5 +114,4 @@ object Tokens { val LITERALS = Set(CHARACTER_LITERAL, INTEGER_LITERAL, FLOATING_POINT_LITERAL, STRING_LITERAL, STRING_PART, SYMBOL_LITERAL, TRUE, FALSE, NULL) -} - +} \ No newline at end of file diff --git a/scalariform/src/main/scala/scalariform/parser/AstNodes.scala b/scalariform/src/main/scala/scalariform/parser/AstNodes.scala index 23988e7f..c62a8c0b 100644 --- a/scalariform/src/main/scala/scalariform/parser/AstNodes.scala +++ b/scalariform/src/main/scala/scalariform/parser/AstNodes.scala @@ -445,5 +445,4 @@ case class XmlProcessingInstruction(token: Token) extends XmlContents { lazy val case class XmlExpr(first: XmlContents, otherElements: List[XmlContents]) extends ExprElement { lazy val tokens = flatten(first, otherElements) -} - +} \ No newline at end of file diff --git a/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala index a2e106a6..efa45510 100644 --- a/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala @@ -28,8 +28,8 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { |}""" """{ - |case x => - |while (true) { + |case x => + |while (true) { |1 |} |}""" ==> @@ -89,8 +89,8 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { "a match { case b => ; c }" ==> "a match { case b => ; c }" // See issue #60 - """a match { - |case b => + """a match { + |case b => |val c = d |case e => |}""" ==> @@ -99,7 +99,7 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { | val c = d | case e => |}""" - + """a match { |/* foo*/ |case x if z=> 1 @@ -186,7 +186,7 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { |}""" """a match { - | case b + | case b |=> 1 | case ccc => 2 |}""" ==> @@ -217,7 +217,7 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { { - implicit val formattingPreferences = + implicit val formattingPreferences = FormattingPreferences.setPreference(AlignSingleLineCaseStatements, true).setPreference(RewriteArrowSymbols, true) """a match { @@ -228,12 +228,11 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { | case b ⇒ 42 | case ccc ⇒ 24 |}""" - } { - implicit val formattingPreferences = + implicit val formattingPreferences = FormattingPreferences .setPreference(AlignSingleLineCaseStatements, true) .setPreference(AlignSingleLineCaseStatements.MaxArrowIndent, 5) @@ -306,4 +305,4 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { | case elem @ Multi(values @ _*) => |}""" -} +} \ No newline at end of file diff --git a/scalariform/src/test/scala/scalariform/formatter/CommentFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/CommentFormatterTest.scala index 073096f2..a0838b02 100644 --- a/scalariform/src/test/scala/scalariform/formatter/CommentFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/CommentFormatterTest.scala @@ -201,6 +201,13 @@ class CommentFormatterTest extends AbstractFormatterTest { """/** | */ |""" - } + """/** This method applies f to each + | * element of the given list. + | */""" ==> + """/** This method applies f to each + | * element of the given list. + | */ + |""" + } } diff --git a/scalariform/src/test/scala/scalariform/formatter/ForExprFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/ForExprFormatterTest.scala index 39c5084c..44092fdd 100644 --- a/scalariform/src/test/scala/scalariform/formatter/ForExprFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/ForExprFormatterTest.scala @@ -140,14 +140,16 @@ class ForExprFormatterTest extends AbstractExpressionFormatterTest { |yield n)""" ==> """Some( | for (n <- 1 to 10) - | yield n)""" + | yield n + |)""" """Some( |for (n <- 1 to 10) |proc())""" ==> """Some( | for (n <- 1 to 10) - | proc())""" + | proc() + |)""" } diff --git a/scalariform/src/test/scala/scalariform/formatter/ImportFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/ImportFormatterTest.scala index cc824044..cc376716 100644 --- a/scalariform/src/test/scala/scalariform/formatter/ImportFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/ImportFormatterTest.scala @@ -2,6 +2,7 @@ package scalariform.formatter import scalariform.parser._ import scalariform.formatter._ +import scalariform.formatter.preferences.{SpacesAroundMultiImports, FormattingPreferences} // format: OFF class ImportFormatterTest extends AbstractFormatterTest { @@ -18,6 +19,14 @@ class ImportFormatterTest extends AbstractFormatterTest { | wibble => wobble |}""" + + { + implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesAroundMultiImports, false) + + "import foo.{bar=>baz}" ==> "import foo.{bar => baz}" + "import foo.{bar=>baz},baz.biz" ==> "import foo.{bar => baz}, baz.biz" + } + override val debug = false type Result = CompilationUnit diff --git a/scalariform/src/test/scala/scalariform/formatter/MiscExpressionFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/MiscExpressionFormatterTest.scala index bb85f5a3..885b5eaa 100644 --- a/scalariform/src/test/scala/scalariform/formatter/MiscExpressionFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/MiscExpressionFormatterTest.scala @@ -7,429 +7,450 @@ import scalariform.formatter.preferences._ // format: OFF class MiscExpressionFormatterTest extends AbstractExpressionFormatterTest { - "→" ==> "→" - "this" ==> "this" - "super [ B ] . t" ==> "super[B].t" - "E2 . super [ B ] . t" ==> "E2.super[B].t" - "1" ==> "1" - "true" ==> "true" - "2.345" ==> "2.345" - "2 + 2" ==> "2 + 2" - - "a max(b)" ==> "a max (b)" - "-5f max(2)" ==> "-5f max (2)" - "-5 max(2)" ==> "-5 max (2)" - "-5f.max(2)" ==> "-5f.max(2)" - "-5.max(2)" ==> "-5.max(2)" - - "42" ==> "42" - "-42" ==> "-42" - - "- ~" ==> "- ~" - - """println("hello")""" ==> """println("hello")""" - "1 * (2 + 3) * 4" ==> "1 * (2 + 3) * 4" - - """println(getClass().getSimpleName() + " passed.")""" ==> - """println(getClass().getSimpleName() + " passed.")""" - - "a(b).c" ==> "a(b).c" - - "b = 2" ==> "b = 2" - "b_+= = 2" ==> "b_+= = 2" - "2: Int" ==> "2: Int" - "x: _*" ==> "x: _*" - "x: _ *" ==> "x: _*" - "x_ : Int" ==> "x_ : Int" - "|v| : Int" ==> "|v| : Int" - "×× : (A, A)" ==> "×× : (A, A)" - - "{ case (_~_) => }" ==> "{ case (_ ~ _) => }" - "{ case (a~_) => }" ==> "{ case (a ~ _) => }" - "{ case (_~b) => }" ==> "{ case (_ ~ b) => }" - - "(1, 2, 3, 4)" ==> "(1, 2, 3, 4)" - "(x: Int, y: List[String], z: Map[_ <: K, V])" ==> "(x: Int, y: List[String], z: Map[_ <: K, V])" - - "a(3)=5" ==> "a(3) = 5" - - "-3 + -2" ==> "-3 + -2" - "-3+(-2)" ==> "-3 + (-2)" - "-a * +b * ~c * !d" ==> "-a * +b * ~c * !d" - "FOO?" ==> "FOO?" - "x_ ?" ==> "x_ ?" - "palindrome reverse" ==> "palindrome reverse" - - "_" ==> "_" - "println _" ==> "println _" - "s.isInstanceOf[String]" ==> "s.isInstanceOf[String]" - - "placeholderParams = treeCopy.ValDef(vd, mods, name, tpt.duplicate, EmptyTree) :: rest" ==> - "placeholderParams = treeCopy.ValDef(vd, mods, name, tpt.duplicate, EmptyTree) :: rest" - - "in.token == ARROW && (location != InTemplate || lhsIsTypedParamList)" ==> - "in.token == ARROW && (location != InTemplate || lhsIsTypedParamList)" - - "stripParens(reduceStack(true, base, topinfo.operand, 0, true))" ==> - "stripParens(reduceStack(true, base, topinfo.operand, 0, true))" - - "surround(open, close)(enumerators(), Nil)" ==> "surround(open, close)(enumerators(), Nil)" - - "makeColour(red = 253, green = 712, blue = 120)" ==> "makeColour(red = 253, green = 712, blue = 120)" - - "_ => 3" ==> "_ => 3" - "(_: Int) => 3" ==> "(_: Int) => 3" - "(x: String, y: Map[String, String]) => y(x)" ==> "(x: String, y: Map[String, String]) => y(x)" - - """2: @Foo({println("bar") - |3})""" ==> - """2: @Foo({ - | println("bar") - | 3 - |})""" - - """f: { def n: Int - |def m: Int}""" ==> - """f: { - | def n: Int - | def m: Int - |}""" - - - """List[Int { val n: Int - |val m: Int }]()""" ==> - """List[Int { - | val n: Int - | val m: Int - |}]()""" - - - """42 match { - |case x: Int { val n: Int - |val m: Int } => 42 - |}""" ==> - """42 match { - | case x: Int { - | val n: Int - | val m: Int - | } => 42 - |}""" - - { - implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesWithinPatternBinders, false) + "→" ==> "→" + "this" ==> "this" + "super [ B ] . t" ==> "super[B].t" + "E2 . super [ B ] . t" ==> "E2.super[B].t" + "1" ==> "1" + "true" ==> "true" + "2.345" ==> "2.345" + "2 + 2" ==> "2 + 2" - """42 match { - | case foo_ @Bar => - |}""" ==> - """42 match { - | case foo_ @Bar => - |}""" + "a max(b)" ==> "a max (b)" + "-5f max(2)" ==> "-5f max (2)" + "-5 max(2)" ==> "-5 max (2)" + "-5f.max(2)" ==> "-5f.max(2)" + "-5.max(2)" ==> "-5.max(2)" - } + "42" ==> "42" + "-42" ==> "-42" - "NEWLINE" ==> "NEWLINE" - "NEWLINES" ==> "NEWLINES" + "- ~" ==> "- ~" - "\"\"\"triplequoted\"\"\"" ==> "\"\"\"triplequoted\"\"\"" + """println("hello")""" ==> """println("hello")""" + "1 * (2 + 3) * 4" ==> "1 * (2 + 3) * 4" - "{ type x = Equal[App[Lam[X], X]#Eval, X] }" ==> "{ type x = Equal[App[Lam[X], X]#Eval, X] }" + """println(getClass().getSimpleName() + " passed.")""" ==> + """println(getClass().getSimpleName() + " passed.")""" - """{ - | type U = Int - | type T <: Seq[U] - |}""" ==> - """{ - | type U = Int - | type T <: Seq[U] - |}""" + "a(b).c" ==> "a(b).c" - "{ type X[+T]=List[T]}" ==> "{ type X[+T] = List[T] }" + "b = 2" ==> "b = 2" + "b_+= = 2" ==> "b_+= = 2" + "2: Int" ==> "2: Int" + "x: _*" ==> "x: _*" + "x: _ *" ==> "x: _*" + "x_ : Int" ==> "x_ : Int" + "|v| : Int" ==> "|v| : Int" + "×× : (A, A)" ==> "×× : (A, A)" - "new A(b, c)" ==> "new A(b, c)" + "{ case (_~_) => }" ==> "{ case (_ ~ _) => }" + "{ case (a~_) => }" ==> "{ case (a ~ _) => }" + "{ case (_~b) => }" ==> "{ case (_ ~ b) => }" - "1/2" ==> "1 / 2" - "1+/2" ==> "1 +/ 2" - "1+/+2" ==> "1 +/+ 2" - "1*/2" ==> "1 */ 2" + "(1, 2, 3, 4)" ==> "(1, 2, 3, 4)" + "(x: Int, y: List[String], z: Map[_ <: K, V])" ==> "(x: Int, y: List[String], z: Map[_ <: K, V])" - "1*/*a*/2" ==> "1 * /*a*/ 2" - "1 +/* /* var */ var */2" ==> "1 + /* /* var */ var */ 2" - "1 + /* /* var */ var */2" ==> "1 + /* /* var */ var */ 2" + "a(3)=5" ==> "a(3) = 5" - """(1//2 - |+2)""" ==> - """(1 //2 - | + 2)""" // TODO: Review - - "1.to(13)" ==> "1.to(13)" - "0.asInstanceOf[Character]" ==> "0.asInstanceOf[Character]" + "-3 + -2" ==> "-3 + -2" + "-3+(-2)" ==> "-3 + (-2)" + "-a * +b * ~c * !d" ==> "-a * +b * ~c * !d" + "FOO?" ==> "FOO?" + "x_ ?" ==> "x_ ?" + "palindrome reverse" ==> "palindrome reverse" - "b match { case _: List[List[C]] => d }" ==> "b match { case _: List[List[C]] => d }" + "_" ==> "_" + "println _" ==> "println _" + "s.isInstanceOf[String]" ==> "s.isInstanceOf[String]" - """for { - | n <- Some(42) - | _ <- Some(42) - |} yield n""" ==> - """for { - | n <- Some(42) - | _ <- Some(42) - |} yield n""" + "placeholderParams = treeCopy.ValDef(vd, mods, name, tpt.duplicate, EmptyTree) :: rest" ==> + "placeholderParams = treeCopy.ValDef(vd, mods, name, tpt.duplicate, EmptyTree) :: rest" - "foo(implicit x => implicitly[X])" ==> "foo(implicit x => implicitly[X])" + "in.token == ARROW && (location != InTemplate || lhsIsTypedParamList)" ==> + "in.token == ARROW && (location != InTemplate || lhsIsTypedParamList)" - "(x: Int) => 42" ==> "(x: Int) => 42" + "stripParens(reduceStack(true, base, topinfo.operand, 0, true))" ==> + "stripParens(reduceStack(true, base, topinfo.operand, 0, true))" - "tail.partition(_ "tail.partition(_ < pivot)" + "surround(open, close)(enumerators(), Nil)" ==> "surround(open, close)(enumerators(), Nil)" - "postWorkItem { () => }" ==> "postWorkItem { () => }" + "makeColour(red = 253, green = 712, blue = 120)" ==> "makeColour(red = 253, green = 712, blue = 120)" - """new C(new D { - |println("foo") - |println("bar") - |})""" ==> - """new C(new D { - | println("foo") - | println("bar") - |})""" - - { - implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesWithinPatternBinders, false) - - """b match { - |case y@ => - |}""" ==> - """b match { - | case y@ => - |}""" // TODO: Whitespace around @ in this case? - } - - """1 / // foo - |2""" ==> - """1 / // foo - | 2""" - - "(USCORE ~ opt(wildcardType) |/ /* id |/ */typ_)" ==> "(USCORE ~ opt(wildcardType) |/ /* id |/ */ typ_)" - - "a match {case b => c; case d => e}" ==> "a match { case b => c; case d => e }" - - "Option(foo) match { case Some(x)=> 42 case None => 12}" ==> - "Option(foo) match { case Some(x) => 42 case None => 12 }" - - """a match { - |case b|(c) => - |}""" ==> - """a match { - | case b | (c) => - |}""" + """a( + |)""" ==> """a()""" - """new B - |{ - |println("foo") - |}""" ==> - """new B { - | println("foo") - |}""" - - """println - |{foo - |}""" ==> - """println { - | foo - |}""" - - """println - |{ - | foo}""" ==> - """println { - | foo - |}""" - - """println - |{foo}""" ==> - """println { foo }""" - - - """doBlock(xs) {(x:Int) => println(x) - |println("bobble") - |}""" ==> - """doBlock(xs) { (x: Int) => - | println(x) - | println("bobble") - |}""" - - """doBlock(xs) {x => println(x) - |println("bobble") - |}""" ==> - """doBlock(xs) { x => - | println(x) - | println("bobble") - |}""" - - """doBlock(xs) {(x:Int) => println(x)}""" ==> """doBlock(xs) { (x: Int) => println(x) }""" - - """foo - |{ bar } - |{ baz }""" ==> - """foo { bar } { baz }""" - - """foo { bar } { baz }""" ==> """foo { bar } { baz }""" - - """foo { () => - |}""" ==> - """foo { () => - |}""" - - """3 +// foo - |4""" ==> - """3 + // foo - | 4""" - - """3 + - |{ 4 * 12 - |}""" ==> - """3 + - | { - | 4 * 12 - | }""" - - """1 + - |2 + - |3""" ==> - """1 + - | 2 + - | 3""" + "_ => 3" ==> "_ => 3" + "(_: Int) => 3" ==> "(_: Int) => 3" + "(x: String, y: Map[String, String]) => y(x)" ==> "(x: String, y: Map[String, String]) => y(x)" + + """2: @Foo({println("bar") + |3})""" ==> + """2: @Foo({ + | println("bar") + | 3 + |})""" + + """f: { def n: Int + |def m: Int}""" ==> + """f: { + | def n: Int + | def m: Int + |}""" + + + """List[Int { val n: Int + |val m: Int }]()""" ==> + """List[Int { + | val n: Int + | val m: Int + |}]()""" + + + """42 match { + |case x: Int { val n: Int + |val m: Int } => 42 + |}""" ==> + """42 match { + | case x: Int { + | val n: Int + | val m: Int + | } => 42 + |}""" + + { + implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesWithinPatternBinders, false) + + """42 match { + | case foo_ @Bar => + |}""" ==> + """42 match { + | case foo_ @Bar => + |}""" + + } + + "NEWLINE" ==> "NEWLINE" + "NEWLINES" ==> "NEWLINES" + + "\"\"\"triplequoted\"\"\"" ==> "\"\"\"triplequoted\"\"\"" + + "{ type x = Equal[App[Lam[X], X]#Eval, X] }" ==> "{ type x = Equal[App[Lam[X], X]#Eval, X] }" + + """{ + | type U = Int + | type T <: Seq[U] + |}""" ==> + """{ + | type U = Int + | type T <: Seq[U] + |}""" + + "{ type X[+T]=List[T]}" ==> "{ type X[+T] = List[T] }" + + "new A(b, c)" ==> "new A(b, c)" + + "1/2" ==> "1 / 2" + "1+/2" ==> "1 +/ 2" + "1+/+2" ==> "1 +/+ 2" + "1*/2" ==> "1 */ 2" + + "1*/*a*/2" ==> "1 * /*a*/ 2" + "1 +/* /* var */ var */2" ==> "1 + /* /* var */ var */ 2" + "1 + /* /* var */ var */2" ==> "1 + /* /* var */ var */ 2" + + """(1//2 + |+2)""" ==> + """(1 //2 + | + 2)""" // TODO: Review + + "1.to(13)" ==> "1.to(13)" + "0.asInstanceOf[Character]" ==> "0.asInstanceOf[Character]" + + "b match { case _: List[List[C]] => d }" ==> "b match { case _: List[List[C]] => d }" + + """for { + | n <- Some(42) + | _ <- Some(42) + |} yield n""" ==> + """for { + | n <- Some(42) + | _ <- Some(42) + |} yield n""" + + "foo(implicit x => implicitly[X])" ==> "foo(implicit x => implicitly[X])" + + "(x: Int) => 42" ==> "(x: Int) => 42" + + "tail.partition(_ "tail.partition(_ < pivot)" + + "postWorkItem { () => }" ==> "postWorkItem { () => }" + + """new C(new D { + |println("foo") + |println("bar") + |})""" ==> + """new C(new D { + | println("foo") + | println("bar") + |})""" + + { + implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesWithinPatternBinders, false) + + """b match { + |case y@ => + |}""" ==> + """b match { + | case y@ => + |}""" // TODO: Whitespace around @ in this case? + } + + """1 / // foo + |2""" ==> + """1 / // foo + | 2""" + + "(USCORE ~ opt(wildcardType) |/ /* id |/ */typ_)" ==> "(USCORE ~ opt(wildcardType) |/ /* id |/ */ typ_)" + + "a match {case b => c; case d => e}" ==> "a match { case b => c; case d => e }" + + "Option(foo) match { case Some(x)=> 42 case None => 12}" ==> + "Option(foo) match { case Some(x) => 42 case None => 12 }" + + """a match { + |case b|(c) => + |}""" ==> + """a match { + | case b | (c) => + |}""" + + """new B + |{ + |println("foo") + |}""" ==> + """new B { + | println("foo") + |}""" + + """println + |{foo + |}""" ==> + """println { + | foo + |}""" + + """println + |{ + | foo}""" ==> + """println { + | foo + |}""" + + """println + |{foo}""" ==> + """println { foo }""" + + + """doBlock(xs) {(x:Int) => println(x) + |println("bobble") + |}""" ==> + """doBlock(xs) { (x: Int) => + | println(x) + | println("bobble") + |}""" + + """doBlock(xs) {x => println(x) + |println("bobble") + |}""" ==> + """doBlock(xs) { x => + | println(x) + | println("bobble") + |}""" + + """doBlock(xs) {(x:Int) => println(x)}""" ==> """doBlock(xs) { (x: Int) => println(x) }""" + + """foo + |{ bar } + |{ baz }""" ==> + """foo { bar } { baz }""" + + """foo { bar } { baz }""" ==> """foo { bar } { baz }""" + + """foo { () => + |}""" ==> + """foo { () => + |}""" + + """3 +// foo + |4""" ==> + """3 + // foo + | 4""" + + """3 + + |{ 4 * 12 + |}""" ==> + """3 + + | { + | 4 * 12 + | }""" + + """1 + + |2 + + |3""" ==> + """1 + + | 2 + + | 3""" """foo(1, |2)""" ==> - """foo(1, - | 2)""" + """foo( + | 1, + | 2 + |)""" - """/* a */ - |b""" ==> - """/* a */ b""" + """/* a */ + |b""" ==> + """/* a */ b""" """a( |if (b) c)""" ==> """a( - | if (b) c)""" + | if (b) c + |)""" """a( |if (b) |c)""" ==> """a( | if (b) - | c)""" + | c + |)""" - """a("A", + """a("A", | b("B", | c(1, 2), - | c(3, 4)), - | b("B2", + | c(3, 4)), + | b("B2", | c(5, 6)))""" ==> - """a("A", - | b("B", + """a( + | "A", + | b( + | "B", | c(1, 2), - | c(3, 4)), - | b("B2", - | c(5, 6)))""" + | c(3, 4) + | ), + | b( + | "B2", + | c(5, 6) + | ) + |)""" """1 + (a, | b, c)""" ==> - """1 + (a, - | b, c)""" - - """1 + ( - | a, b, c)""" ==> """1 + ( - | a, b, c)""" - - """1 + (a - |, b, c)""" ==> - """1 + (a, b, c)""" - - "()" ==> "()" - - { - implicit val formattingPreferences = FormattingPreferences.setPreference(SpaceBeforeColon, true) - "(a: Int) => 3" ==> "(a : Int) => 3" - } - - """{ // format: +spaceBeforeColon - | val a:Int = 2 - |}""" ==> - """{ // format: +spaceBeforeColon - | val a : Int = 2 - |}""" + | a, + | b, c + |)""" - "_.a" ==> "_.a" - - { - implicit val formattingPreferences = FormattingPreferences.setPreference(CompactStringConcatenation, true) - """"foo"+"bar"""" ==> """"foo"+"bar"""" - """"foo" + "bar"""" ==> """"foo"+"bar"""" - """foo + "bar"""" ==> """foo+"bar"""" - """"foo" + bar""" ==> """"foo"+bar""" - """foo + bar""" ==> """foo + bar""" - } - - """a { implicit b => - |println(b) - |}""" ==> - """a { implicit b => - | println(b) - |}""" - - """a { b => - |println(b) - |}""" ==> - """a { b => - | println(b) - |}""" + """1 + ( + | a, b, c)""" ==> + """1 + ( + | a, b, c + |)""" + + """1 + (a + |, b, c)""" ==> + """1 + (a, b, c)""" + + "()" ==> "()" + + { + implicit val formattingPreferences = FormattingPreferences.setPreference(SpaceBeforeColon, true) + "(a: Int) => 3" ==> "(a : Int) => 3" + } + + """{ // format: +spaceBeforeColon + | val a:Int = 2 + |}""" ==> + """{ // format: +spaceBeforeColon + | val a : Int = 2 + |}""" + + "_.a" ==> "_.a" + + { + implicit val formattingPreferences = FormattingPreferences.setPreference(CompactStringConcatenation, true) + """"foo"+"bar"""" ==> """"foo"+"bar"""" + """"foo" + "bar"""" ==> """"foo"+"bar"""" + """foo + "bar"""" ==> """foo+"bar"""" + """"foo" + bar""" ==> """"foo"+bar""" + """foo + bar""" ==> """foo + bar""" + } + + """a { implicit b => + |println(b) + |}""" ==> + """a { implicit b => + | println(b) + |}""" + + """a { b => + |println(b) + |}""" ==> + """a { b => + | println(b) + |}""" """a(b, |/* c */d)""" ==> - """a(b, - | /* c */ d)""" + """a( + | b, + | /* c */ d + |)""" - """submit( - | - |)""" ==> - """submit()""" + """submit( + | + |)""" ==> + """submit()""" """( - |42, + |42, |46 |)""" ==> """( | 42, - | 46)""" // I prefer no initial indent for tuples, although you could argue it should be consistent with ParenExprs - + | 46 + |)""" // I prefer no initial indent for tuples, although you could argue it should be consistent with ParenExprs + """a(b, |c => { |d})""" ==> - """a(b, + """a( + | b, | c => { | d - | })""" + | } + |)""" - """a(b, + """a(b, |(c), { |d})""" ==> - """a(b, + """a( + | b, | (c), { | d - | })""" + | } + |)""" """a( - | () => + | () => | b)""" ==> """a( | () => - | b)""" - + | b + |)""" - { - implicit val formattingPreferences = FormattingPreferences.setPreference(PreserveDanglingCloseParenthesis, true) """Book( | name = "Name", @@ -442,350 +463,540 @@ class MiscExpressionFormatterTest extends AbstractExpressionFormatterTest { | rating = 5 |)""" - } - - """Book( - | name = "Name", - | author = "Author", - | rating = 5 - |)""" ==> - """Book( - | name = "Name", - | author = "Author", - | rating = 5)""" - - - """foobar( + """foobar( |(1,2), - |(3, 4), - |(5, 6), + |(3, 4), + |(5, 6), |(7, 8))""" ==> """foobar( | (1, 2), | (3, 4), | (5, 6), - | (7, 8))""" + | (7, 8) + |)""" - """(1 - |,2)""" ==> - """(1, 2)""" + """(1 + |,2)""" ==> + """(1, 2)""" - """a(1 - |,2)""" ==> - """a(1, 2)""" + """a(1 + |,2)""" ==> + """a(1, 2)""" """a( - |b, - |c + + |b, + |c + |d)""" ==> """a( | b, | c + - | d)""" - - """(a -> - |new B)""" ==> - """(a -> - | new B)""" - - """(1 - | + { - |foo - |})""" ==> - """(1 - | + { - | foo - | })""" - - """a + - | b + (c + - | d)""" ==> - """a + - | b + (c + - | d)""" - - """42 - |: Int""" ==> - """42: Int""" - - "if (true) 1; else 2" ==> "if (true) 1; else 2" // Check SEMI + ELSE rule - - "a: ::" ==> "a: ::" - - - """(a - | + b - |+ c)""" ==> - """(a - | + b - | + c)""" - - """(a + - | b + - |c)""" ==> - """(a + - | b + - | c)""" - - """(a + - |( b + - |c))""" ==> - """(a + - | (b + - | c))""" - - """a + - |b + - |c""" ==> - """a + - | b + - | c""" - - """a match { - | case wibble( - | wobble( - | wubble(x))) => y - | }""" ==> - """a match { - | case wibble( - | wobble( - | wubble(x))) => y - |}""" - - """'a' - |.b - |.c""" ==> - """'a' - | .b - | .c""" - - """a. - |b().c match { - |case d => - |}""" ==> - """a. - | b().c match { - | case d => - | }""" - - """a() - |.b(c( - |d))""" ==> - """a() - | .b(c( - | d))""" - - """a(). - |b(c => { - |d - |})""" ==> - """a(). - | b(c => { | d - | })""" - - """((a).b - |{ - | c - |})""" ==> - """((a).b { - | c - |})""" - - """(a).b - |{ - | c - |}""" ==> - """(a).b { - | c - |}""" - - """('a'.b - | (c))""" ==> - """('a'.b(c))""" - - """a match { - |case b if c && - |d => {e - |} - |}""" =/=> - """a match { - | case b if c && - | d => { - | e - | } - |}""" because " we don't thread the expression break state through case statement" - - """a = - |b""" ==> - """a = - | b""" - """a = - |b + c + - |d""" ==> - """a = - | b + c + - | d""" - - """a - |.b = - |c""" ==> - """a - | .b = - | c""" - - """a - |.b = { - |c - |}""" ==> - """a - | .b = { - | c - | }""" - - """(f - | (b))""" ==> - """(f(b))""" - - { - - implicit val formattingPreferences = FormattingPreferences.setPreference(PreserveSpaceBeforeArguments, true) - - "getDirectives(source) should be (expectedDirectives)" ==> - "getDirectives(source) should be (expectedDirectives)" - - "getDirectives(source) should be(expectedDirectives)" ==> - "getDirectives(source) should be(expectedDirectives)" + |)""" - } - """(if (a) b else c - |, d)""" ==> - """(if (a) b else c, d)""" + """(a -> + |new B)""" ==> + """(a -> + | new B)""" + + """(1 + | + { + |foo + |})""" ==> + """(1 + | + { + | foo + | })""" + + """a + + | b + (c + + | d)""" ==> + """a + + | b + (c + + | d)""" + + """42 + |: Int""" ==> + """42: Int""" + + "if (true) 1; else 2" ==> "if (true) 1; else 2" // Check SEMI + ELSE rule + + "a: ::" ==> "a: ::" + + + """(a + | + b + |+ c)""" ==> + """(a + | + b + | + c)""" + + """(a + + | b + + |c)""" ==> + """(a + + | b + + | c)""" + + """(a + + |( b + + |c))""" ==> + """(a + + | (b + + | c))""" + + """a + + |b + + |c""" ==> + """a + + | b + + | c""" + + """a match { + | case wibble( + | wobble( + | wubble(x))) => y + | }""" ==> + """a match { + | case wibble( + | wobble( + | wubble(x))) => y + |}""" + + """'a' + |.b + |.c""" ==> + """'a' + | .b + | .c""" + +"""a. + |b().c match { + |case d => + |}""" ==> +"""a. + | b().c match { + | case d => + | }""" + + """a() + |.b(c( + |d))""" ==> + """a() + | .b(c( + | d + | ))""" + + """a(). + |b(c => { + |d + |})""" ==> + """a(). + | b(c => { + | d + | })""" + + """((a).b + |{ + | c + |})""" ==> + """((a).b { + | c + |})""" + + """(a).b + |{ + | c + |}""" ==> + """(a).b { + | c + |}""" + + """('a'.b + | (c))""" ==> + """('a'.b(c))""" + + """a match { + |case b if c && + |d => {e + |} + |}""" =/=> + """a match { + | case b if c && + | d => { + | e + | } + |}""" because " we don't thread the expression break state through case statement" + + """a = + |b""" ==> + """a = + | b""" + """a = + |b + c + + |d""" ==> + """a = + | b + c + + | d""" + + """a + |.b = + |c""" ==> + """a + | .b = + | c""" + + """a + |.b = { + |c + |}""" ==> + """a + | .b = { + | c + | }""" + + """(f + | (b))""" ==> + """(f(b))""" + + { + + implicit val formattingPreferences = FormattingPreferences.setPreference(PreserveSpaceBeforeArguments, true) + + "getDirectives(source) should be (expectedDirectives)" ==> + "getDirectives(source) should be (expectedDirectives)" + + "getDirectives(source) should be(expectedDirectives)" ==> + "getDirectives(source) should be(expectedDirectives)" + + } + + """(if (a) b else c + |, d)""" ==> + """(if (a) b else c, d)""" + + """a(b, + |c) + { + |d + |}""" ==> + """a( + | b, + | c + |) + { + | d + |}""" + + """(b, + |c) + { + |d}""" ==> + """( + | b, + | c + |) + { + | d + | }""" + + """ XScalaWT.shell("title", + | label("label"), + | popupMenu( + | viewer( + | // TODO + | ) + | ) + | )""" ==> + """XScalaWT.shell( + | "title", + | label("label"), + | popupMenu( + | viewer( // TODO + | ) + | ) + |)""" + + """a(b) + |.c(d) + |.e(f) + |.g""" ==> + """a(b) + | .c(d) + | .e(f) + | .g""" + + """(a, + |b) + (c, + |d) + (e, + | f) + g""" ==> + """( + | a, + | b + |) + ( + | c, + | d + | ) + ( + | e, + | f + | ) + g""" + + """(a, b)""" ==> """(a, b)""" + """a + (b, + |c) + + |d""" ==> + """a + ( + | b, + | c + |) + + | d""" + + """a + f(b, + | c) + + |d""" ==> + """a + f( + | b, + | c + |) + + | d""" + + """a + |.b( + |c) + |.d""" ==> + """a + | .b( + | c + | ) + | .d""" + + + "Foo.this" ==> "Foo.this" + + """List.range(1, r) flatMap ( + | i => List.range(1, i) map (j => (i, j)) + |)""" ==> + """List.range(1, r) flatMap ( + | i => List.range(1, i) map (j => (i, j)) + |)""" + + """a map { + | b => + | c + | d + |}""" ==> + """a map { + | b => + | c + | d + |}""" + + """Things.foreach { + | thing => + | println("_%s".format(thing)) + |}""" ==> + """Things.foreach { + | thing => + | println("_%s".format(thing)) + |}""" // See issue 21 + + """a.map { + | b => c + |}""" ==> + """a.map { + | b => c + |}""" + + "f()[Foo]" ==> "f()[Foo]" + "a [ b . C ] [ d . E ] [ f . G ] " ==> "a[b.C][d.E][f.G]" + + "{ val P(a, b*c) = p }" ==> "{ val P(a, b * c) = p }" + + + """new {} with A(new { + |val x = 42}) with B(new { + |val x = 42})""" ==> + """new {} with A(new { + | val x = 42 + |}) with B(new { + | val x = 42 + |})""" - """a(b, - |c) + { + """a( + |b, + |c, |d - |}""" ==> - """a(b, - | c) + { - | d - | }""" - - """(b, - |c) + { - |d}""" ==> - """(b, - | c) + { - | d - | }""" - - """ XScalaWT.shell("title", - | label("label"), - | popupMenu( - | viewer( - | // TODO - | ) - | ) - | )""" ==> - """XScalaWT.shell("title", - | label("label"), - | popupMenu( - | viewer( // TODO - | )))""" - - """a(b) - |.c(d) - |.e(f) - |.g""" ==> - """a(b) - | .c(d) - | .e(f) - | .g""" - - """(a, - |b) + (c, - |d) + (e, - | f) + g""" ==> - """(a, - | b) + (c, - | d) + (e, - | f) + g""" - - """a + (b, - |c) + - |d""" ==> - """a + (b, - | c) + - | d""" - - """a + f(b, - | c) + - |d""" ==> - """a + f(b, - | c) + - | d""" - - """a - |.b( - |c) - |.d""" ==> - """a - | .b( - | c) - | .d""" - - - "Foo.this" ==> "Foo.this" - - """List.range(1, r) flatMap ( - | i => List.range(1, i) map (j => (i, j)) + |)( + | e, + | f, + | g |)""" ==> - """List.range(1, r) flatMap ( - | i => List.range(1, i) map (j => (i, j)))""" + """a( + | b, + | c, + | d + |)( + | e, + | f, + | g + |)""" - """a map { - | b => - | c - | d - |}""" ==> - """a map { - | b => - | c - | d - |}""" - - """Things.foreach { - | thing => - | println("_%s".format(thing)) - |}""" ==> - """Things.foreach { - | thing => - | println("_%s".format(thing)) - |}""" // See issue 21 - - """a.map { - | b => c - |}""" ==> - """a.map { - | b => c - |}""" - - "f()[Foo]" ==> "f()[Foo]" - "a [ b . C ] [ d . E ] [ f . G ] " ==> "a[b.C][d.E][f.G]" - - "{ val P(a, b*c) = p }" ==> "{ val P(a, b * c) = p }" - - - """new {} with A(new { - |val x = 42}) with B(new { - |val x = 42})""" ==> - """new {} with A(new { - | val x = 42 - |}) with B(new { - | val x = 42 - |})""" - - override val debug = false - + { + implicit val formattingPreferences = FormattingPreferences.setPreference(AlignArguments, true) + + """Method(a, b, c)""" ==> """Method(a, b, c)""" + + // Force a newline if arguments are multi line + """Method(aaa = "", + |bb = 1, + |c = null)""" ==> + """Method( + | aaa = "", + | bb = 1, + | c = null + |)""" + + """method(multiLineArgument = { + | val string = "hello world" + | println(string) + |}, + |b = 1, + |c = null)""" =/=> + """method( + | multiLineArgument = { + | val string = "hello world" + | println(string) + | }, + | shortParam = 1, + | shorter = null)""" because "TODO: this didn't work before AlignArguments, but is worth fixing" + + """o.p(aaaaa(x_x,z,y), + | b(x, + | yy, + | zzz( + | a, + | b, + | c + | ) + | ), + |c(firstGroupOne(x)), + | d, + | e, + | f + |)""" ==> + """o.p( + | aaaaa(x_x, z, y), + | b( + | x, + | yy, + | zzz( + | a, + | b, + | c + | ) + | ), + | c(firstGroupOne(x)), + | d, + | e, + | f + |)""" + + """o.manyArguments( + | abc = 0, + | abcOne = 1, + | abcTwo, + | abcThree = 3, + | abcFour = 4, + | abcFive = 3 + |)""" ==> + """o.manyArguments( + | abc = 0, + | abcOne = 1, + | abcTwo, + | abcThree = 3, + | abcFour = 4, + | abcFive = 3 + |)""" + + """Nested1(abcccc = 1, + | abc2 = 2, + | abcThree = 3 + |)""" ==> + """Nested1( + | abcccc = 1, + | abc2 = 2, + | abcThree = 3 + |)""" + + """o.grouped( + | firstGroupOne = 1, + | firstGroup2 = "One", + | + | secondGroupOne = 2, + | secondGroup2 = "Two", + | + | thirdGroupOne = 3, + | thirdGroup2 = Three(3) + |)""" ==> + """o.grouped( + | firstGroupOne = 1, + | firstGroup2 = "One", + | + | secondGroupOne = 2, + | secondGroup2 = "Two", + | + | thirdGroupOne = 3, + | thirdGroup2 = Three(3) + |)""" + + """multiClause( + | arg1 = 1, + | argument2 = 2)( + | args3 = 3, + | arg4 = 4)( + | arg5 = 5)""" ==> + """multiClause( + | arg1 = 1, + | argument2 = 2 + |)( + | args3 = 3, + | arg4 = 4 + |)( + | arg5 = 5 + |)""" + + """a( + |b = c)( + |c, + |d)( + |d)""" ==> + """a( + | b = c + |)( + | c, + | d + |)( + | d + |)""" + + """Nested0( + | arg1 = Nested1(abcccc = 1, + | abc2 = 2, + | abcThree = 3 + | ), + | NestedTwo(abcccc = 1, + | abc2 = 2, + | abcThree = 3 + | ) + |)""" ==> + """Nested0( + | arg1 = Nested1( + | abcccc = 1, + | abc2 = 2, + | abcThree = 3 + | ), + | NestedTwo( + | abcccc = 1, + | abc2 = 2, + | abcThree = 3 + | ) + |)""" } + override val debug = false +} \ No newline at end of file diff --git a/scalariform/src/test/scala/scalariform/formatter/MiscFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/MiscFormatterTest.scala new file mode 100644 index 00000000..3aae6386 --- /dev/null +++ b/scalariform/src/test/scala/scalariform/formatter/MiscFormatterTest.scala @@ -0,0 +1,46 @@ +package scalariform.formatter + +import scalariform.parser.{FullDefOrDcl, ScalaParser} + +class MiscFormatterTest extends AbstractFormatterTest { + + """class Foo( + | bar: String, + | baz: String + |)""" ==> + """class Foo( + | bar: String, + | baz: String + |)""" + + """class Foo( + | bar: String, + | baz: String)""" ==> + """class Foo( + | bar: String, + | baz: String + |)""" + + """class Foo( + |)""" ==> + """class Foo()""" + + """class a()""" ==> + """class a()""" + + """def a()""" ==> + """def a()""" + + """class a(b: Int)""" ==> + """class a(b: Int)""" + + """def a(b: Int)""" ==> + """def a(b: Int)""" + + def parse(parser: ScalaParser) = parser.nonLocalDefOrDcl() + + type Result = FullDefOrDcl + + def format(formatter: ScalaFormatter, result: Result) = formatter.format(result)(FormatterState(indentLevel = 0)) + +} \ No newline at end of file diff --git a/scalariform/src/test/scala/scalariform/formatter/PackageFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/PackageFormatterTest.scala index 4634e8d5..ae340c07 100644 --- a/scalariform/src/test/scala/scalariform/formatter/PackageFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/PackageFormatterTest.scala @@ -60,8 +60,5 @@ class PackageFormatterTest extends AbstractFormatterTest { |class Baz |} |}""" - } - - } diff --git a/scalariform/src/test/scala/scalariform/formatter/TemplateFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/TemplateFormatterTest.scala index 972bfbef..22b3c69f 100644 --- a/scalariform/src/test/scala/scalariform/formatter/TemplateFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/TemplateFormatterTest.scala @@ -7,77 +7,77 @@ import scalariform.formatter.preferences._ // format: OFF class TemplateFormatterTest extends AbstractFormatterTest { - "case class A" ==> "case class A" +"case class A" ==> "case class A" - """class A extends B { - | foo() - | bar() - |}""" ==> - """class A extends B { - | foo() - | bar() - |}""" +"""class A extends B { + | foo() + | bar() + |}""" ==> +"""class A extends B { + | foo() + | bar() + |}""" - """class A { - |val n: Int - |}""" ==> - """class A { - | val n: Int - |}""" +"""class A { + |val n: Int + |}""" ==> +"""class A { + | val n: Int + |}""" - "class A" ==> "class A" - "trait A" ==> "trait A" - - "class A private (val b: C)" ==> "class A private (val b: C)" - "class A [B]" ==> "class A[B]" - "class A[B]private(val c: D)" ==> "class A[B] private (val c: D)" - "class A (val b: C) (val d: E) (implicit val f: G)" ==> "class A(val b: C)(val d: E)(implicit val f: G)" - "class A (implicit val f: G)" ==> "class A(implicit val f: G)" - - "abstract class E [F]private(val h: I) (implicit j: K) extends{} with L(2) with M{}" ==> - "abstract class E[F] private (val h: I)(implicit j: K) extends {} with L(2) with M {}" - - "class A{}" ==> "class A {}" - - "class A@Deprecated()private (val b: C)" ==> "class A @Deprecated() private (val b: C)" - "class A@Annotation1()@Annotation2()(val b: C)" ==> "class A @Annotation1() @Annotation2() (val b: C)" - "class A[B]@Annotation()private(val c: D)" ==> "class A[B] @Annotation() private (val c: D)" - - "class A @Deprecated" ==> "class A @Deprecated" - "class A @Deprecated private" ==> "class A @Deprecated private" - "class A @Deprecated private (n: Int)" ==> "class A @Deprecated private (n: Int)" - - """@A@B(c = "d")abstract class E [F]@G()private(val h: I) (implicit j: K) extends{} with L(2) with M{}""" ==> - """@A @B(c = "d") abstract class E[F] @G() private (val h: I)(implicit j: K) extends {} with L(2) with M {}""" - - """@A/*a*/@B - |/*b*/class E""" =/=> - """@A/*a*/ - |@B - |/*b*/ - |class E""" because "of inconsistency between spacing between annotations and comments" - - """/*a*/@A/*b*/@B(c = "d")/*c*/abstract class/*d*/E/*e*/[F]/*f*/@G()/*g*/private/*h*/(val h: I)/*i*/(implicit j: K)/*j*/extends/*k*/{} with/*l*/L(2) with M/*m*/{}""" =/=> - """/*a*/ - |@A/*b*/ - |@B(c = "d")/*c*/ - |abstract class/*d*/E/*e*/[F]/*f*/@G()/*g*/private/*h*/(val h: I)/*i*/(implicit j: K)/*j*/extends/*k*/{} with/*l*/L(2) with M/*m*/{}""" because "sort out what we want" - +"class A" ==> "class A" +"trait A" ==> "trait A" - { - - implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesWithinPatternBinders, true) +"class A private (val b: C)" ==> "class A private (val b: C)" +"class A [B]" ==> "class A[B]" +"class A[B]private(val c: D)" ==> "class A[B] private (val c: D)" +"class A (val b: C) (val d: E) (implicit val f: G)" ==> "class A(val b: C)(val d: E)(implicit val f: G)" +"class A (implicit val f: G)" ==> "class A(implicit val f: G)" + +"abstract class E [F]private(val h: I) (implicit j: K) extends{} with L(2) with M{}" ==> + "abstract class E[F] private (val h: I)(implicit j: K) extends {} with L(2) with M {}" + +"class A{}" ==> "class A {}" + +"class A@Deprecated()private (val b: C)" ==> "class A @Deprecated() private (val b: C)" +"class A@Annotation1()@Annotation2()(val b: C)" ==> "class A @Annotation1() @Annotation2() (val b: C)" +"class A[B]@Annotation()private(val c: D)" ==> "class A[B] @Annotation() private (val c: D)" + +"class A @Deprecated" ==> "class A @Deprecated" +"class A @Deprecated private" ==> "class A @Deprecated private" +"class A @Deprecated private (n: Int)" ==> "class A @Deprecated private (n: Int)" + +"""@A@B(c = "d")abstract class E [F]@G()private(val h: I) (implicit j: K) extends{} with L(2) with M{}""" ==> +"""@A @B(c = "d") abstract class E[F] @G() private (val h: I)(implicit j: K) extends {} with L(2) with M {}""" + +"""@A/*a*/@B + |/*b*/class E""" =/=> +"""@A/*a*/ + |@B + |/*b*/ + |class E""" because "of inconsistency between spacing between annotations and comments" + +"""/*a*/@A/*b*/@B(c = "d")/*c*/abstract class/*d*/E/*e*/[F]/*f*/@G()/*g*/private/*h*/(val h: I)/*i*/(implicit j: K)/*j*/extends/*k*/{} with/*l*/L(2) with M/*m*/{}""" =/=> +"""/*a*/ + |@A/*b*/ + |@B(c = "d")/*c*/ + |abstract class/*d*/E/*e*/[F]/*f*/@G()/*g*/private/*h*/(val h: I)/*i*/(implicit j: K)/*j*/extends/*k*/{} with/*l*/L(2) with M/*m*/{}""" because "sort out what we want" + + +{ + +implicit val formattingPreferences = FormattingPreferences.setPreference(SpacesWithinPatternBinders, true) + +"@(Id@Field) class A" ==> "@(Id @Field) class A" + +} - "@(Id@Field) class A" ==> "@(Id @Field) class A" - - } - """class A { - | + | | class B - | + | | protected def c - | + | |}""" ==> """class A { | @@ -88,7 +88,7 @@ class TemplateFormatterTest extends AbstractFormatterTest { |}""" """class A{ - |( + |( |null match { |case b => val c = {d: Int => 1} |1.toString @@ -101,7 +101,8 @@ class TemplateFormatterTest extends AbstractFormatterTest { | case b => | val c = { d: Int => 1 } | 1.toString - | }) + | } + | ) |}""" """class C1492 { @@ -156,7 +157,7 @@ class TemplateFormatterTest extends AbstractFormatterTest { |}""" """trait A { - |this: B => + |this: B => |val c |}""" ==> """trait A { @@ -164,8 +165,8 @@ class TemplateFormatterTest extends AbstractFormatterTest { | val c |}""" - """trait A { - |this: B => + """trait A { + |this: B => |println("foo") |}""" ==> """trait A { @@ -179,12 +180,12 @@ class TemplateFormatterTest extends AbstractFormatterTest { "@serializable class A" ==> "@serializable class A" "@volatile var nParticles = 0" ==> "@volatile var nParticles = 0" // Issue #28 - + """@volatile |var nParticles = 0""" ==> """@volatile |var nParticles = 0""" - + """class A extends B[C] { |println("foo") |}""" ==> @@ -287,9 +288,9 @@ class TemplateFormatterTest extends AbstractFormatterTest { "class A {\r\n b()\r\n}" ==> "class A {\r\n b()\r\n}" """class A( - |m: Int, + |m: Int, |n: { def open(): Unit - |def close(): Unit}, + |def close(): Unit}, |o: Int)""" ==> """class A( | m: Int, @@ -297,12 +298,13 @@ class TemplateFormatterTest extends AbstractFormatterTest { | def open(): Unit | def close(): Unit | }, - | o: Int)""" + | o: Int + |)""" """class A( |n: Int, m: {def foo(): Int |def bar(a: String): Int}, o: Int, - |p: Int, + |p: Int, |m: {def foo(): Int |def bar(a: String): Int})""" ==> """class A( @@ -314,22 +316,234 @@ class TemplateFormatterTest extends AbstractFormatterTest { | m: { | def foo(): Int | def bar(a: String): Int - | })""" + | } + |)""" + +{ + implicit val formattingPreferences = FormattingPreferences. + setPreference(AlignParameters, true). + setPreference(SpaceBeforeColon, true). + setPreference(SpaceInsideBrackets, true) + + """def a( + |a: Int = 1, + |abc: Boolean = true): Int""" ==> + """def a( + | a : Int = 1, + | abc : Boolean = true + |) : Int""" + + """def a( + |a: Option[Either[Int]] = 1, + |abc: Boolean = true): Int""" ==> + """def a( + | a : Option[ Either[ Int ] ] = 1, + | abc : Boolean = true + |) : Int""" +} - { +{ + implicit val formattingPreferences = FormattingPreferences. + setPreference(AlignParameters, true). + setPreference(RewriteArrowSymbols, true) + + // Formats rewritten arrows correctly + """def A( + | a: A => B = null, + | bee: => B = null, + | c: B => C = null + |): D""" ==> + """def A( + | a: A ⇒ B = null, + | bee: ⇒ B = null, + | c: B ⇒ C = null + |): D""" + + """class a( + | b: Int + |)""" ==> + """class a( + | b: Int + |)""" + + """class a( + | a: String = "", + | b: Int = 0 + |)( + | c: String = "", + | d: Int = 1 + |)( + | implicit + | val e: String = "", + | f: Int = 2 + |)""" =/=> + """class a( + | a: String = "" + | b: Int = 0 + |)( + | c: String = "" + | d: Int = 1 + |)( + | implicit + | val e: String = "" + | f: Int = 2 + |)""" +} + +{ implicit val formattingPreferences = FormattingPreferences.setPreference(AlignParameters, true) - """class A(n: Int, + + // Make sure spacing in same line implicit parameters is preserved + """class A[T](a: T)(implicit b: B)""" ==> + """class A[T](a: T)(implicit b: B)""" + """class A[T](a: T)(implicit b: B, c: C)""" ==> + """class A[T](a: T)(implicit b: B, c: C)""" + """class A[T](a: T)(implicit b: B, c: C, + |d: D)""" ==> + """class A[T](a: T)(implicit b: B, c: C, + | d: D)""" + + // Split into 3 columns: name, type, and default + """def showInput[A]( + | parent: Component = null, + | message: Any, + | title: String = uiString("OptionPane.inputDialogTitle"), + | messageType: Message.Value = Message.Question, + | icon: Icon = EmptyIcon, + | entries: Seq[A] = Nil, + | initial: A): Option[A]""" ==> + """def showInput[A]( + | parent: Component = null, + | message: Any, + | title: String = uiString("OptionPane.inputDialogTitle"), + | messageType: Message.Value = Message.Question, + | icon: Icon = EmptyIcon, + | entries: Seq[A] = Nil, + | initial: A + |): Option[A]""" + + // Formats function types correctly + """private def executeWithinClient[T]( + |crawlerConfig: String => JsValue = Fancy.function, + |f: HttpCrawlerClient => T, + |port: Int = SpecHelper.port): T""" ==> + """private def executeWithinClient[T]( + | crawlerConfig: String => JsValue = Fancy.function, + | f: HttpCrawlerClient => T, + | port: Int = SpecHelper.port + |): T""" + + // By name parameters have correct spacing + """def a( + | p1: => (SomeLongByNameParam => SomeShorterParam) = Null, + | param2: SomeShorterParam = Null): A""" ==> + """def a( + | p1: => (SomeLongByNameParam => SomeShorterParam) = Null, + | param2: SomeShorterParam = Null + |): A""" + + // Formats parameterized types correctly + """def A(complicatedType: Option[B ,C, D[E, F,G]] = None, + | simpleType: String = ""): B""" ==> + """def A( + | complicatedType: Option[B, C, D[E, F, G]] = None, + | simpleType: String = "" + |): B""" + + + // Param gets placed onto a new line due to current limitations of existing IntertokenFormatInstructions + """case class Spacing(param: Int = 1, + |paramTwo: Int = 2, + |paramThree: String = "3")""" ==> + """case class Spacing( + | param: Int = 1, + | paramTwo: Int = 2, + | paramThree: String = "3" + |)""" + + // Groups and formats consecutive single line parameters (multi line params) + """case class Spacing(param: Int = 1, + |paramTwo: Int = 2, + |paramThree: { + | val test: Int + |}, + |paramFour: Option[String] = Some("One"), + |paramFive: Any = Nothing)""" ==> + """case class Spacing( + | param: Int = 1, + | paramTwo: Int = 2, + | paramThree: { + | val test: Int + | }, + | paramFour: Option[String] = Some("One"), + | paramFive: Any = Nothing + |)""" + + // Groups and formats consecutive single line parameters (newlines) + """case class Spacing( + |param: Int = 1, + |paramTwo: Int = 2, + | + |paramFour: Option[String] = Some("One"), + |paramFive: Any = Nothing)""" ==> + """case class Spacing( + | param: Int = 1, + | paramTwo: Int = 2, + | + | paramFour: Option[String] = Some("One"), + | paramFive: Any = Nothing + |)""" + + // Aligns implicits and curried parameters properly + """class SomeClass( + |parameterOne: Int = 1, + |val parameterTwo: Option[String] = None, + |three: String = "three")( + |intermediate: Int + |)( + |implicit val four: Int, + |five: String, + |six: Boolean)""" ==> + """class SomeClass( + | parameterOne: Int = 1, + | val parameterTwo: Option[String] = None, + | three: String = "three" + |)( + | intermediate: Int + |)( + | implicit + | val four: Int, + | five: String, + | six: Boolean + |)""" + + // Handles annotations, modifiers, and comments + """def extraStuff( + |// comment 1 + |@Annotated paramOne: Int = 1, // comment 2 + |/* comment 3 */ private val modifiedTwo: String = "two", + |@Annotated2("complicatedAnnotation") @A3("Another") protected annotatedAndModified: Option[Int] = Some(3))""" ==> + """def extraStuff( + | // comment 1 + | @Annotated paramOne: Int = 1, // comment 2 + | /* comment 3 */ private val modifiedTwo: String = "two", + | @Annotated2("complicatedAnnotation")@A3("Another") protected annotatedAndModified: Option[Int] = Some(3) + |)""" + + """class A(n: Int, |z: { val m |val n }, |m: Int)""" ==> - """class A(n: Int, - | z: { - | val m - | val n - | }, - | m: Int)""" - - """class A(m: Int, + """class A( + | n: Int, + | z: { + | val m + | val n + | }, + | m: Int + |)""" + + """class A(m: Int, |n: { |val x: String |val y : String @@ -337,38 +551,43 @@ class TemplateFormatterTest extends AbstractFormatterTest { | o: Int = { | 42 |})""" ==> - """class A(m: Int, - | n: { - | val x: String - | val y: String - | }, - | o: Int = { - | 42 - | })""" + """class A( + | m: Int, + | n: { + | val x: String + | val y: String + | }, + | o: Int = { + | 42 + | } + |)""" """class A(n: { | def close(): Unit | def open(): Unit - |}, + |}, |m: Int)""" ==> - """class A(n: { - | def close(): Unit - | def open(): Unit - | }, - | m: Int)""" - + """class A( + | n: { + | def close(): Unit + | def open(): Unit + | }, + | m: Int + |)""" + """class A( |implicit n: { |def x: Int |def y: Int |})""" ==> """class A( - | implicit n: { + | implicit + | n: { | def x: Int | def y: Int - | })""" - - + | } + |)""" + """class A(n: { |def x: Int |})""" ==> @@ -376,53 +595,59 @@ class TemplateFormatterTest extends AbstractFormatterTest { | def x: Int | })""" - """class A(a: Int, - |b: Int)(c: { val d: Int - |})""" ==> """class A(a: Int, - | b: Int)(c: { - | val d: Int - | })""" - - } - - """class A(a: Int, |b: Int)(c: { val d: Int |})""" ==> - """class A(a: Int, - | b: Int)(c: { + """class A( + | a: Int, + | b: Int + |)(c: { | val d: Int | })""" + } + + """class A(a: Int, + |b: Int)(c: { val d: Int + |})""" ==> + """class A( + | a: Int, + | b: Int + |)(c: { + | val d: Int + |})""" + { implicit val formattingPreferences = FormattingPreferences.setPreference(DoubleIndentClassDeclaration, true) - """class Person( - | name: String, - | age: Int) - | extends Entity - | with Logging - | with Identifiable - | with Serializable""" ==> - """class Person( - | name: String, - | age: Int) - | extends Entity - | with Logging - | with Identifiable - | with Serializable""" + """class Person( + | name: String, + | age: Int) + | extends Entity + | with Logging + | with Identifiable + | with Serializable""" ==> + """class Person( + | name: String, + | age: Int + |) + | extends Entity + | with Logging + | with Identifiable + | with Serializable""" + + """class Person( + | name: String, + | age: Int) { + | def firstMethod = 42 + |}""" ==> + """class Person( + | name: String, + | age: Int + |) { + | def firstMethod = 42 + |}""" - """class Person( - | name: String, - | age: Int) { - | def firstMethod = 42 - |}""" ==> - """class Person( - | name: String, - | age: Int) { - | def firstMethod = 42 - |}""" - - """class Person(name: String, age: Int, birthdate: Date, astrologicalSign: String, shoeSize: Int, favoriteColor: java.awt.Color) + """class Person(name: String, age: Int, birthdate: Date, astrologicalSign: String, shoeSize: Int, favoriteColor: java.awt.Color) |extends Entity |with Logging |with Identifiable @@ -437,21 +662,22 @@ class TemplateFormatterTest extends AbstractFormatterTest { | def firstMethod = 42 |}""" - """class Person( - |name: String, - | age: Int) - |extends Entity { - |def method() = 42 - |}""" ==> - """class Person( - | name: String, - | age: Int) - | extends Entity { - | def method() = 42 - |}""" + """class Person( + |name: String, + | age: Int) + |extends Entity { + |def method() = 42 + |}""" ==> + """class Person( + | name: String, + | age: Int + |) + | extends Entity { + | def method() = 42 + |}""" - """trait A - |extends B + """trait A + |extends B |with C { |println("d") |}""" ==> @@ -462,11 +688,11 @@ class TemplateFormatterTest extends AbstractFormatterTest { |}""" } - + "trait Function1[@specialized(Int, Long, Double) -T1, @specialized(Unit, Int, Long, Double) +R] extends AnyRef" ==> "trait Function1[@specialized(Int, Long, Double) -T1, @specialized(Unit, Int, Long, Double) +R] extends AnyRef" - - """class C + +"""class C |extends { |val name = "Bob" |}""" ==> @@ -607,7 +833,7 @@ class TemplateFormatterTest extends AbstractFormatterTest { | bar() |}""" - // format: ON +// format: ON override val debug = false @@ -617,4 +843,4 @@ class TemplateFormatterTest extends AbstractFormatterTest { def format(formatter: ScalaFormatter, result: Result) = formatter.format(result)(FormatterState(indentLevel = 0)) -} +} \ No newline at end of file