Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scalafmt: move line ending choice to FormatWriter #4097

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5319,9 +5319,7 @@ takes the following values:
- `windows`: uses CRLF (`U+000D U+000A`)
- `preserve`: if an input file _contains_ CRLF anywhere, use CRLF for every line; otherwise, LF

```scala mdoc:defaults
lineEndings
```
By default, this parameter is assumed to be set to `unix`.

### `rewriteTokens`

Expand Down
23 changes: 1 addition & 22 deletions scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.scalafmt

import org.scalafmt.Error.PreciseIncomplete
import org.scalafmt.config.FormatEvent.CreateFormatOps
import org.scalafmt.config.LineEndings
import org.scalafmt.config.NamedDialect
import org.scalafmt.config.ScalafmtConfig
import org.scalafmt.internal.BestFirstSearch
Expand Down Expand Up @@ -30,8 +29,6 @@ import metaconfig.Configured
*/
object Scalafmt {

private val WinLineEnding = "\r\n"
private val UnixLineEnding = "\n"
private val defaultFilename = "<input>"

// XXX: don't modify signature, scalafmt-dynamic expects it via reflection
Expand Down Expand Up @@ -78,28 +75,10 @@ object Scalafmt {
else baseStyle.getConfigFor(filename).map(getStyleByFile)
styleTry.fold(
Formatted.Result(_, baseStyle),
formatCodeWithStyle(code, _, range, filename),
x => Formatted.Result(doFormat(code, x, filename, range), x),
)
}

private def formatCodeWithStyle(
code: String,
style: ScalafmtConfig,
range: Set[Range],
filename: String,
): Formatted.Result = {
val isWin = code.contains(WinLineEnding)
val unixCode =
if (isWin) code.replaceAll(WinLineEnding, UnixLineEnding) else code
val res = doFormat(unixCode, style, filename, range).map { x =>
val s = if (x.isEmpty) UnixLineEnding else x
val asWin = style.lineEndings == LineEndings.windows ||
(isWin && style.lineEndings == LineEndings.preserve)
if (asWin) s.replaceAll(UnixLineEnding, WinLineEnding) else s
}
Formatted.Result(res, style)
}

private def doFormat(
code: String,
style: ScalafmtConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ case class ScalafmtConfig(
align: Align = Align(),
spaces: Spaces = Spaces(),
literals: Literals = Literals(),
lineEndings: LineEndings = LineEndings.unix,
lineEndings: Option[LineEndings] = None,
rewriteTokens: Map[String, String] = Map.empty[String, String],
rewrite: RewriteSettings = RewriteSettings.default,
indentOperator: IndentOperator = IndentOperator(),
Expand Down Expand Up @@ -175,6 +175,9 @@ case class ScalafmtConfig(
NamedDialect.getName(dialect).getOrElse("unknown dialect"),
)

def withLineEndings(value: LineEndings): ScalafmtConfig =
copy(lineEndings = Option(value))

private lazy val forMain: ScalafmtConfig =
if (project.layout.isEmpty) forTest
else rewrite.forMainOpt.fold(this)(x => copy(rewrite = x))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ case class FormatToken(left: Token, right: Token, meta: FormatToken.Meta) {
@inline
def hasBreakOrEOF: Boolean = hasBreak || right.is[Token.EOF]

def hasCRLF: Boolean = between.exists {
case _: Token.CRLF => true
case t: Token.MultiNL => t.tokens.exists(_.is[Token.CRLF])
case _ => false
}

/** A format token is uniquely identified by its left token.
*/
override def hashCode(): Int = hash(left).##
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.scalafmt.Scalafmt
import org.scalafmt.config.Comments
import org.scalafmt.config.Docstrings
import org.scalafmt.config.FormatEvent
import org.scalafmt.config.LineEndings
import org.scalafmt.config.Newlines
import org.scalafmt.config.RewriteScala3Settings
import org.scalafmt.config.ScalafmtConfig
Expand Down Expand Up @@ -106,12 +107,19 @@ class FormatWriter(formatOps: FormatOps) {
val depth = state.depth
require(toks.length >= depth, "splits !=")
val result = new Array[FormatLocation](depth)
// 1 yes, 0 tbd, -1 no
var useCRLF = initStyle.lineEndings.fold(-1) {
case LineEndings.unix => -1
case LineEndings.windows => 1
case LineEndings.preserve => 0
}

@tailrec
def iter(cur: State, lineId: Int, gapId: Int): Unit = {
val prev = cur.prev
val idx = prev.depth
val ft = toks(idx)
if (useCRLF == 0 && ft.hasCRLF) useCRLF = 1
if (idx == 0) // done
result(idx) = FormatLocation(ft, cur, initStyle, lineId, gapId)
else {
Expand Down Expand Up @@ -139,7 +147,7 @@ class FormatWriter(formatOps: FormatOps) {
) replaceRedundantBraces(result)
}

new FormatLocations(result, "\n")
new FormatLocations(result, if (useCRLF > 0) "\r\n" else "\n")
}

private def replaceRedundantBraces(locations: Array[FormatLocation]): Unit = {
Expand Down
35 changes: 27 additions & 8 deletions scalafmt-tests/src/test/scala/org/scalafmt/EmptyFileTest.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.scalafmt

import org.scalafmt.config.LineEndings
import org.scalafmt.config.ScalafmtConfig

import scala.meta.internal.prettyprinters.DoubleQuotes

import java.lang.System.lineSeparator
Expand All @@ -8,16 +11,32 @@ import munit.FunSuite

class EmptyFileTest extends FunSuite {

private val cfgWithLineEndingsCRLF = ScalafmtConfig.default
.withLineEndings(LineEndings.windows)
private val cfgWithLineEndingsKeep = ScalafmtConfig.default
.withLineEndings(LineEndings.preserve)

Seq(
("", "\n"),
(" \n \n ", "\n"),
(" \r\n \r\n ", "\n"),
(lineSeparator(), "\n"),
(s" $lineSeparator ", "\n"),
).foreach { case (original, expected) =>
("", "\n", "\r\n", "\n"),
(" \n \n ", "\n", "\r\n", "\n"),
(" \r\n \r\n ", "\n", "\r\n", "\r\n"),
(lineSeparator(), "\n", "\r\n", lineSeparator()),
(s" $lineSeparator ", "\n", "\r\n", lineSeparator()),
).foreach { case (original, expectedUnix, expectedCrlf, expectedKeep) =>
defineTest(original, expectedUnix, ScalafmtConfig.default, "unix")
defineTest(original, expectedCrlf, cfgWithLineEndingsCRLF, "crlf")
defineTest(original, expectedKeep, cfgWithLineEndingsKeep, "keep")
}

private def defineTest(
original: String,
expected: String,
cfg: ScalafmtConfig,
label: String,
): Unit = {
val expectedQuoted = DoubleQuotes(expected)
test(s"empty tree formats to newline: ${DoubleQuotes(original)} -> $expectedQuoted") {
val obtained = Scalafmt.format(original).get
test(s"empty tree formats to newline [$label]: ${DoubleQuotes(original)} -> $expectedQuoted") {
val obtained = Scalafmt.format(original, cfg).get
if (obtained != expected) fail(
s"values are not equal: ${DoubleQuotes(obtained)} != $expectedQuoted",
)
Expand Down
12 changes: 6 additions & 6 deletions scalafmt-tests/src/test/scala/org/scalafmt/LineEndingsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,47 @@ class LineEndingsTest extends FunSuite {
val original = "@ Singleton\r\nobject a {\r\nval y = 2\r\n}"
val expected = "@Singleton\r\nobject a {\r\n val y = 2\r\n}\r\n"
val obtained = Scalafmt
.format(original, ScalafmtConfig.default.copy(lineEndings = preserve)).get
.format(original, ScalafmtConfig.default.withLineEndings(preserve)).get
assertNoDiff(obtained, expected)
}

test("code with unix line endings after formatting with line endings preserve setting should have the same endings") {
val original = "@ Singleton\nobject a {\nval y = 2\n}"
val expected = "@Singleton\nobject a {\n val y = 2\n}\n"
val obtained = Scalafmt
.format(original, ScalafmtConfig.default.copy(lineEndings = preserve)).get
.format(original, ScalafmtConfig.default.withLineEndings(preserve)).get
assertNoDiff(obtained, expected)
}

test("code with windows line endings after formatting with line endings windows setting should have windows endings") {
val original = "@ Singleton\r\nobject a {\r\nval y = 2\r\n}"
val expected = "@Singleton\r\nobject a {\r\n val y = 2\r\n}\r\n"
val obtained = Scalafmt
.format(original, ScalafmtConfig.default.copy(lineEndings = windows)).get
.format(original, ScalafmtConfig.default.withLineEndings(windows)).get
assertNoDiff(obtained, expected)
}

test("code with unix line endings after formatting with line endings windows setting should have windows endings") {
val original = "@ Singleton\nobject a {\nval y = 2\n}"
val expected = "@Singleton\r\nobject a {\r\n val y = 2\r\n}\r\n"
val obtained = Scalafmt
.format(original, ScalafmtConfig.default.copy(lineEndings = windows)).get
.format(original, ScalafmtConfig.default.withLineEndings(windows)).get
assertNoDiff(obtained, expected)
}

test("code with windows line endings after formatting with line endings unix setting should have unix endings") {
val original = "@ Singleton\r\nobject a {\r\nval y = 2\r\n}"
val expected = "@Singleton\nobject a {\n val y = 2\n}\n"
val obtained = Scalafmt
.format(original, ScalafmtConfig.default.copy(lineEndings = unix)).get
.format(original, ScalafmtConfig.default.withLineEndings(unix)).get
assertNoDiff(obtained, expected)
}

test("code with unix line endings after formatting with line endings unix setting should have unix endings") {
val original = "@ Singleton\nobject a {\nval y = 2\n}"
val expected = "@Singleton\nobject a {\n val y = 2\n}\n"
val obtained = Scalafmt
.format(original, ScalafmtConfig.default.copy(lineEndings = unix)).get
.format(original, ScalafmtConfig.default.withLineEndings(unix)).get
assertNoDiff(obtained, expected)
}
}
Loading