The Scruff style

Scruff aims for consistency, generality, readability, and reducing git diffs.

This document describes the current formatting style.

How Scruff wraps lines

Scruff ignores previous formatting, and applies uniform horizontal whitespace to your code based on max-column (default 100). Scruff uses 2 whitespaces for indentation, like most Scala styles.

Short expressions

Scruff tries to print one full expression or a statement per line. If this fits the allotted max-column, it will flatten the lines together.

// original input
object A:
  val a = List(1, 2)
  val b = List(
    1,
    2
  )
  val c =
    List(1, 2)

becomes:

// scruffed
object A:
  val a = List(1, 2)
  val b = List(1, 2)
  val c = List(1, 2)

Expressions that can fold in half

When a definition doesn't fit on one line, Scruff first tries folding it in half at the =:

def compileIncremental(setup: Setup, previous: PreviousResult): CompileResult = compileWith(setup, previous, inputs, reporter)

becomes

// scruffed
def compileIncremental(setup: Setup, previous: PreviousResult): CompileResult =
  compileWith(setup, previous, inputs, reporter)

Long expressions

If a function call still doesn't fit after folding, Scruff puts one argument per line, with a trailing comma:

val result = doSomething(compileInputs, logger, reporter, progress, cacheDirectory, extraOptions, previousResult)

becomes

// scruffed
val result = doSomething(
  compileInputs,
  logger,
  reporter,
  progress,
  cacheDirectory,
  extraOptions,
  previousResult,
)

Note

Scruff will add trailing commas to long function calls.

Named arguments

A function call with named arguments, or with a DSL containing = signs, puts one argument per line:

val conn = connect(host = "localhost", port = 8080)

lazy val action = project
  .settings(name := "action", Test / fork := true)

becomes

// scruffed
val conn = connect(
  host = "localhost",
  port = 8080,
)

lazy val action = project
  .settings(
    name := "action",
    Test / fork := true,
  )

Call chain (fluent interface)

A chain of two or more method calls and field selections stays on one line if it fits. Otherwise, Scruff breaks before each ., starting from the first call. Field selection, like xs.dependencies, stay with the receiver:

val names = xs.dependencies.filter(_.isTransitive).map(_.moduleName).filterNot(excluded).distinct.sorted.take(limit)

becomes

// scruffed
val names = xs.dependencies
  .filter(_.isTransitive)
  .map(_.moduleName)
  .filterNot(excluded)
  .distinct
  .sorted
  .take(limit)

Chained infix expression

A chain of infix expressions stays on one line if it fits. Otherwise, Scruff breaks before each && or || operator, so the operators start the lines:

val skip = isCi && isPullRequest || offline && cache.isEmpty || dryRun && warningCount + errorCount == 0

becomes

// scruffed
val skip = isCi && isPullRequest
  || offline && cache.isEmpty
  || dryRun && warningCount + errorCount == 0

Scala 3 dialect

Scala 3 is the default dialect of Scruff formatter:

dialect = "scala3"

Scala 3: Indentation syntax

"scala3" diealect replaces braces with indentation where Scala 3 allows it. This covers the bodies of classes, objects, and definitions:

object A {
  def foo(x: Int): String =
    x match {
      case 1 => "one"
      case _ => "many"
    }
}

becomes

// scruffed
object A:
  def foo(x: Int): String = x match
    case 1 => "one"
    case _ => "many"

Scala 3: Fewer braces (colon argument) syntax

"scala3" dialect replaces a braced lambda argument with a colon argument (SIP-44):

val sizes = files.map { file =>
  val bytes = Files.size(file)
  bytes / 1024
}

becomes

// scruffed
val sizes = files.map: file =>
  val bytes = Files.size(file)
  bytes / 1024

Scala 3: Control syntax

"scala3" dialect rewrites to Scala 3 control syntax:

val label = if (count > 1) "many" else "one"
val names = for (file <- files) yield file.getName

becomes

// scruffed
val label = if count > 1 then "many" else "one"
val names = for file <- files yield file.getName

Scala 3: Miscellaneous rewrites

  • * for wildcard imports, and as for renames in imports and exports
  • ? for type wildcards
  • xs* for vararg splices
  • using for implicit parameter clauses
  • & for compound types, and commas between parents after extends
  • End markers added to definitions spanning 20 or more lines, and removed from shorter ones

Scala 2 dialects

Scruff also supports the following Scala 2 dialects:

  • scala213source3, scala212source3, and sbt1 (Scala 2 with -Xsource:3): Scruff rewrites imports, type wildcards, and vararg splices to the Scala 3 syntax, and keeps braces, parentheses, implicit, and with.
  • scala213: Scruff rewrites Scala 3 syntax back to Scala 2 syntax, such as braces, parentheses, _ wildcard imports, => renames, and xs: _* splices.

In both, a Boolean chain broken across lines is wrapped in parentheses, since a Scala 2 line can't start with an operator:

// scruffed
val shouldPublish = (isRelease
  && !isSnapshot
  && credentials.nonEmpty
  && repository.isDefined
  && testsPassed)

Comments

Scruff does not format comment contents.

Scaladoc

Scruff will, however, unify the Scaladoc comments with gutter asterisks aligned in column two:

// scruffed
/** Compiles the sources.
 *
 *  @param sources the files to compile
 */
def compile(sources: Seq[File]): Unit = ()

format: off

Code in between // format: off and // format: on are not formatted:

// format: off

object AnythingGoes {}

// format: on