diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 29eecfeb18..ee06b9a302 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ on:
tags:
- '*'
pull_request:
-
+
jobs:
test:
strategy:
@@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - uses: olafurpg/setup-scala@v6
+ - uses: olafurpg/setup-scala@v7
with:
java-version: ${{ matrix.java }}
- - run: git fetch --tags
+ - run: git fetch --tags -f
- run:
# for GitOps tests
git config --global user.email "scalafmt@scalameta.org" && git config --global user.name "scalafmt"
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - uses: olafurpg/setup-scala@v6
+ - uses: olafurpg/setup-scala@v7
- run: ./scalafmt --test
- run: yarn install
- run: yarn format-check
@@ -40,17 +40,37 @@ jobs:
fail-fast: false
matrix:
os: [macOS-latest, ubuntu-latest]
+ libc: [default, musl]
+ exclude:
+ - os: macOS-latest
+ libc: musl
include:
- os: macOS-latest
- url: https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.0.0/graalvm-ce-java11-darwin-amd64-20.0.0.tar.gz
artifact: scalafmt-macos
+ env:
+ NATIVE_IMAGE_STATIC: false
+ - os: ubuntu-latest
+ libc: default
+ artifact: scalafmt-linux-glibc
+ env:
+ NATIVE_IMAGE_STATIC: true
- os: ubuntu-latest
- url: https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.0.0/graalvm-ce-java11-linux-amd64-20.0.0.tar.gz
- artifact: scalafmt-linux
+ libc: musl
+ artifact: scalafmt-linux-musl
+ env:
+ NATIVE_IMAGE_STATIC: true
+ NATIVE_IMAGE_MUSL: /home/runner/work/scalafmt/scalafmt/bundle
+ env: ${{ matrix.env }}
steps:
- uses: actions/checkout@v2
- - uses: olafurpg/setup-scala@v6
- - run: jabba install graal-custom@20.0=tgz+${{ matrix.url }}
+ - uses: olafurpg/setup-scala@v7
+ with:
+ java-version: graalvm-ce-java11@20.1.0
+ - if: matrix.libc == 'musl'
+ name: Install musl bundle
+ run: |
+ wget https://github.com/gradinac/musl-bundle-example/releases/download/v1.0/musl.tar.gz
+ tar xvf musl.tar.gz
- run: bin/build-native-image.sh
env:
CI: true
@@ -58,3 +78,20 @@ jobs:
with:
name: ${{ matrix.artifact }}
path: scalafmt
+ dockerize:
+ needs: [native-image,test]
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/v')
+ steps:
+ - uses: actions/checkout@v2
+ - run: git fetch --unshallow
+ - uses: actions/download-artifact@v1
+ with:
+ name: scalafmt-linux-musl
+ path: tmp/scalafmt-linux-musl
+ - uses: docker/build-push-action@v1
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+ repository: scalameta/scalafmt
+ tag_with_ref: true
diff --git a/.scalafmt.conf b/.scalafmt.conf
index f01f608022..2d3b0758e0 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,4 +1,4 @@
-version=2.5.0-RC3
+version=2.6.1
project.git = true
project.excludeFilters = [
scalafmt-benchmarks/src/resources,
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000..74dbb1f229
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,6 @@
+FROM alpine
+
+COPY tmp/scalafmt-linux-musl/scalafmt /bin/scalafmt
+RUN chmod +x /bin/scalafmt
+
+ENTRYPOINT ["/bin/scalafmt"]
diff --git a/bin/build-native-image.sh b/bin/build-native-image.sh
index 53548da372..26946df82b 100755
--- a/bin/build-native-image.sh
+++ b/bin/build-native-image.sh
@@ -1,8 +1,5 @@
set -eux
# NOTE(olafur): for some reason `jabba use ...` doesn't seem to work on GH Actions
-export JAVA_HOME=$(jabba which --home graal-custom@20.0)
-export PATH=$JAVA_HOME/bin:$PATH
-echo $JAVA_HOME
which gu
gu install native-image
sbt cli/graalvm-native-image:packageBin
diff --git a/build.sbt b/build.sbt
index 0140efc937..7ae7cb4ac0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -182,24 +182,16 @@ lazy val cli = project
scalacOptions ++= scalacJvmOptions.value,
mainClass in GraalVMNativeImage := Some("org.scalafmt.cli.Cli"),
graalVMNativeImageOptions ++= {
- val reflectionFile =
- Keys.sourceDirectory.in(Compile).value./("graal")./("reflection.json")
- assert(reflectionFile.exists, "no such file: " + reflectionFile)
- List(
- "-H:+ReportUnsupportedElementsAtRuntime",
- "-Dscalafmt.native-image=true",
- "--initialize-at-build-time",
- "--no-server",
- "--enable-http",
- "--enable-https",
- "-H:EnableURLProtocols=http,https",
- "--enable-all-security-services",
- "--no-fallback",
- s"-H:ReflectionConfigurationFiles=$reflectionFile",
- "--allow-incomplete-classpath",
- "-H:+ReportExceptionStackTraces"
- //"--initialize-at-build-time=scala.Function1"
- )
+ sys.env
+ .get("NATIVE_IMAGE_MUSL")
+ .map(path => s"-H:UseMuslC=$path")
+ .toSeq ++
+ sys.env
+ .get("NATIVE_IMAGE_STATIC")
+ .map(_.toBoolean)
+ .filter(identity)
+ .map(_ => "--static")
+ .toSeq
}
)
.dependsOn(coreJVM, dynamic)
@@ -212,7 +204,7 @@ lazy val tests = project
libraryDependencies ++= Seq(
// Test dependencies
CrossVersion.partialVersion(scalaVersion.value) match {
- case Some((2, 13)) => "com.lihaoyi" %% "scalatags" % "0.9.0"
+ case Some((2, 13)) => "com.lihaoyi" %% "scalatags" % "0.9.1"
case _ => "com.lihaoyi" %% "scalatags" % "0.6.8"
},
"org.typelevel" %% "paiges-core" % "0.3.0",
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 5ffab2b8df..24487a7259 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -3,6 +3,153 @@ id: changelog
title: Changelog
---
+## [v2.6.3](https://github.com/scalameta/scalafmt/tree/v2.6.3) (2020-07-10)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.6.2...v2.6.3)
+
+**Merged pull requests:**
+
+- Dependencies: upgrade scalameta to 4.3.19 [\#2087](https://github.com/scalameta/scalafmt/pull/2087) ([kitbellew](https://github.com/kitbellew))
+- Increased maxVisitsPerToken limit [\#2086](https://github.com/scalameta/scalafmt/pull/2086) ([poslegm](https://github.com/poslegm))
+- ScalafmtConfig: implicit with binpack unsupported [\#2084](https://github.com/scalameta/scalafmt/pull/2084) ([kitbellew](https://github.com/kitbellew))
+- Policy: introduce more specific expire strategies [\#2083](https://github.com/scalameta/scalafmt/pull/2083) ([kitbellew](https://github.com/kitbellew))
+- \#1627 \[24\]: FormatOps bugfix: ignore NL before infix for fold/unfold [\#2082](https://github.com/scalameta/scalafmt/pull/2082) ([kitbellew](https://github.com/kitbellew))
+- Router: don't format \#! lines in sbt files [\#2077](https://github.com/scalameta/scalafmt/pull/2077) ([kitbellew](https://github.com/kitbellew))
+- RedundantParens: don't rewrite \(\(\)\), aka Lit.Unit [\#2076](https://github.com/scalameta/scalafmt/pull/2076) ([kitbellew](https://github.com/kitbellew))
+- ScalafmtReflectConfig: invoked .forSbt, not legacy dialect [\#2075](https://github.com/scalameta/scalafmt/pull/2075) ([kitbellew](https://github.com/kitbellew))
+- BestFirstSearch: make the Provided split active [\#2074](https://github.com/scalameta/scalafmt/pull/2074) ([kitbellew](https://github.com/kitbellew))
+- Dependency: upgrade scalameta to 4.3.18 [\#2072](https://github.com/scalameta/scalafmt/pull/2072) ([kitbellew](https://github.com/kitbellew))
+
+## [v2.6.2](https://github.com/scalameta/scalafmt/tree/v2.6.2) (2020-07-05)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.6.1...v2.6.2)
+
+**Merged pull requests:**
+
+- Documentation: describe optIn.configStyleArguments, clarify optIn.breaksInsideChains [\#2068](https://github.com/scalameta/scalafmt/pull/2068) ([kitbellew](https://github.com/kitbellew))
+- Router: merge classic select chain rule with other [\#2067](https://github.com/scalameta/scalafmt/pull/2067) ([kitbellew](https://github.com/kitbellew))
+- Router: indent comment before first select [\#2066](https://github.com/scalameta/scalafmt/pull/2066) ([kitbellew](https://github.com/kitbellew))
+- ScalafmtDynamic: throw once if can't resolve config [\#2065](https://github.com/scalameta/scalafmt/pull/2065) ([kitbellew](https://github.com/kitbellew))
+- Upgrade the scalafmt binary to 2.6.1 [\#2064](https://github.com/scalameta/scalafmt/pull/2064) ([kitbellew](https://github.com/kitbellew))
+- PR conflict: fix test from \#2061 affected by \#2060 [\#2063](https://github.com/scalameta/scalafmt/pull/2063) ([kitbellew](https://github.com/kitbellew))
+- {Token,Tree}Ops: remove unused methods [\#2062](https://github.com/scalameta/scalafmt/pull/2062) ([kitbellew](https://github.com/kitbellew))
+- FormatOps: method to check classic select chain [\#2061](https://github.com/scalameta/scalafmt/pull/2061) ([kitbellew](https://github.com/kitbellew))
+- Policy: preserve original policies and ranges for control [\#2060](https://github.com/scalameta/scalafmt/pull/2060) ([kitbellew](https://github.com/kitbellew))
+- Dockerize [\#2059](https://github.com/scalameta/scalafmt/pull/2059) ([gurinderu](https://github.com/gurinderu))
+- Dependency updates [\#2057](https://github.com/scalameta/scalafmt/pull/2057) ([poslegm](https://github.com/poslegm))
+- Update scalameta, testkit to 4.3.17 [\#2055](https://github.com/scalameta/scalafmt/pull/2055) ([scala-steward](https://github.com/scala-steward))
+- Update sbt-mdoc to 2.2.3 [\#2053](https://github.com/scalameta/scalafmt/pull/2053) ([scala-steward](https://github.com/scala-steward))
+- Update sbt to 1.3.13 [\#2052](https://github.com/scalameta/scalafmt/pull/2052) ([scala-steward](https://github.com/scala-steward))
+- Update sbt-native-packager to 1.7.3 [\#2051](https://github.com/scalameta/scalafmt/pull/2051) ([scala-steward](https://github.com/scala-steward))
+- Update sbt-assembly to 0.15.0 [\#2050](https://github.com/scalameta/scalafmt/pull/2050) ([scala-steward](https://github.com/scala-steward))
+- Remove reference to binPack.parentConstructors being a boolean [\#2049](https://github.com/scalameta/scalafmt/pull/2049) ([emma-burrows](https://github.com/emma-burrows))
+- Router: break between comma and multiline comments [\#2048](https://github.com/scalameta/scalafmt/pull/2048) ([kitbellew](https://github.com/kitbellew))
+- TokenOps: no blank line for scaladoc in expression [\#2047](https://github.com/scalameta/scalafmt/pull/2047) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: single-character one-line scaladoc [\#2046](https://github.com/scalameta/scalafmt/pull/2046) ([kitbellew](https://github.com/kitbellew))
+- Router: keep blank line in empty case clause body [\#2045](https://github.com/scalameta/scalafmt/pull/2045) ([kitbellew](https://github.com/kitbellew))
+- Add build with musl libc [\#2041](https://github.com/scalameta/scalafmt/pull/2041) ([gurinderu](https://github.com/gurinderu))
+- clarify how `.scalafix.conf` & the CLI binary versions relate [\#2040](https://github.com/scalameta/scalafmt/pull/2040) ([bjaglin](https://github.com/bjaglin))
+
+## [v2.6.1](https://github.com/scalameta/scalafmt/tree/v2.6.1) (2020-06-18)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.6.0...v2.6.1)
+
+**Merged pull requests:**
+
+- FormatWriter: fold long oneline javadoc if no wrap [\#2036](https://github.com/scalameta/scalafmt/pull/2036) ([kitbellew](https://github.com/kitbellew))
+- State: for overflow delay, apply no more than once [\#2035](https://github.com/scalameta/scalafmt/pull/2035) ([kitbellew](https://github.com/kitbellew))
+- Router bugfix: respect config style for tuples [\#2034](https://github.com/scalameta/scalafmt/pull/2034) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: fix handling of one-line docstrings [\#2031](https://github.com/scalameta/scalafmt/pull/2031) ([kitbellew](https://github.com/kitbellew))
+- TokenOps fix: blank line between mod and scaladoc [\#2030](https://github.com/scalameta/scalafmt/pull/2030) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: fix empty 1st part of interpolation [\#2029](https://github.com/scalameta/scalafmt/pull/2029) ([kitbellew](https://github.com/kitbellew))
+
+## [v2.6.0](https://github.com/scalameta/scalafmt/tree/v2.6.0) (2020-06-16)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.5.3...v2.6.0)
+
+**Merged pull requests:**
+
+- State: don't penalize strings that always overflow [\#2024](https://github.com/scalameta/scalafmt/pull/2024) ([kitbellew](https://github.com/kitbellew))
+- Router: allow relative formatting of spliced XML [\#2021](https://github.com/scalameta/scalafmt/pull/2021) ([kitbellew](https://github.com/kitbellew))
+- ScalafmtRunner: respect filters with custom files [\#2020](https://github.com/scalameta/scalafmt/pull/2020) ([kitbellew](https://github.com/kitbellew))
+- Scaladoc: support gaps in code, refs with punctuation [\#2018](https://github.com/scalameta/scalafmt/pull/2018) ([kitbellew](https://github.com/kitbellew))
+- TokenOps: look for "format: off" in any comment [\#2017](https://github.com/scalameta/scalafmt/pull/2017) ([kitbellew](https://github.com/kitbellew))
+- Router: penalize NL on breakChainOnFirstMethodDot [\#2016](https://github.com/scalameta/scalafmt/pull/2016) ([kitbellew](https://github.com/kitbellew))
+- RedundantBraces: don't rewrite nested Defn [\#2015](https://github.com/scalameta/scalafmt/pull/2015) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter bugfix: handle asterisks in docstring [\#2014](https://github.com/scalameta/scalafmt/pull/2014) ([kitbellew](https://github.com/kitbellew))
+- Split: represent a newline alternative differently [\#2011](https://github.com/scalameta/scalafmt/pull/2011) ([kitbellew](https://github.com/kitbellew))
+- Router: idempotence of breakChainOnFirstMethodDot [\#2010](https://github.com/scalameta/scalafmt/pull/2010) ([kitbellew](https://github.com/kitbellew))
+- State: fix column positions for multiline strings [\#2009](https://github.com/scalameta/scalafmt/pull/2009) ([kitbellew](https://github.com/kitbellew))
+- Router: merge config-style rule into others [\#2008](https://github.com/scalameta/scalafmt/pull/2008) ([kitbellew](https://github.com/kitbellew))
+- FormatOps bugfix: get TParams correctly, not the Paramss [\#2007](https://github.com/scalameta/scalafmt/pull/2007) ([kitbellew](https://github.com/kitbellew))
+- Scaladoc: support table formatting in FormatWriter [\#2006](https://github.com/scalameta/scalafmt/pull/2006) ([kitbellew](https://github.com/kitbellew))
+- FormatToken: store the syntax of tokens in meta [\#2005](https://github.com/scalameta/scalafmt/pull/2005) ([kitbellew](https://github.com/kitbellew))
+- Documentation: move comments section, add release [\#2002](https://github.com/scalameta/scalafmt/pull/2002) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter bugfix: align strip margin in `s"""|` [\#2001](https://github.com/scalameta/scalafmt/pull/2001) ([kitbellew](https://github.com/kitbellew))
+- \#1627 \[23\]: Router: source=keep try keep NL/noNL in apply\(... [\#2000](https://github.com/scalameta/scalafmt/pull/2000) ([kitbellew](https://github.com/kitbellew))
+- Self formatting with 2.5.3 [\#1999](https://github.com/scalameta/scalafmt/pull/1999) ([poslegm](https://github.com/poslegm))
+- Update scalatest to 3.1.2 [\#1997](https://github.com/scalameta/scalafmt/pull/1997) ([scala-steward](https://github.com/scala-steward))
+- Update sbt-mdoc to 2.2.0 [\#1995](https://github.com/scalameta/scalafmt/pull/1995) ([scala-steward](https://github.com/scala-steward))
+- Update sbt-scalajs to 1.1.0 [\#1994](https://github.com/scalameta/scalafmt/pull/1994) ([scala-steward](https://github.com/scala-steward))
+- Update sbt-native-packager to 1.7.2 [\#1992](https://github.com/scalameta/scalafmt/pull/1992) ([scala-steward](https://github.com/scala-steward))
+- Update scalatags to 0.9.1 [\#1991](https://github.com/scalameta/scalafmt/pull/1991) ([scala-steward](https://github.com/scala-steward))
+- FormatWriter bugfix: comment with empty first line [\#1990](https://github.com/scalameta/scalafmt/pull/1990) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: format multiline docstrings [\#1987](https://github.com/scalameta/scalafmt/pull/1987) ([kitbellew](https://github.com/kitbellew))
+- Dependency: upgrade scalameta to 4.3.13 [\#1986](https://github.com/scalameta/scalafmt/pull/1986) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: fix off-by-one bug in comment wrap [\#1985](https://github.com/scalameta/scalafmt/pull/1985) ([kitbellew](https://github.com/kitbellew))
+- FormatOps: fix finding `=\>` in lambda without args [\#1983](https://github.com/scalameta/scalafmt/pull/1983) ([kitbellew](https://github.com/kitbellew))
+- Docstrings: rename styles, clean up text after `\*` [\#1982](https://github.com/scalameta/scalafmt/pull/1982) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: minor refactoring of comment formatting code [\#1981](https://github.com/scalameta/scalafmt/pull/1981) ([kitbellew](https://github.com/kitbellew))
+- Router: allow folded `extends` if ctor is folded [\#1980](https://github.com/scalameta/scalafmt/pull/1980) ([kitbellew](https://github.com/kitbellew))
+- FormatOps: for source=fold, compact extends/with [\#1979](https://github.com/scalameta/scalafmt/pull/1979) ([kitbellew](https://github.com/kitbellew))
+- Spaces configuration documented [\#1977](https://github.com/scalameta/scalafmt/pull/1977) ([poslegm](https://github.com/poslegm))
+- Documentation: expand docstrings [\#1972](https://github.com/scalameta/scalafmt/pull/1972) ([kitbellew](https://github.com/kitbellew))
+- sbt plugin update [\#1971](https://github.com/scalameta/scalafmt/pull/1971) ([poslegm](https://github.com/poslegm))
+- FormatWriter: implement wrapping of long comments [\#1970](https://github.com/scalameta/scalafmt/pull/1970) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter bugfix: comment format on lookaround [\#1968](https://github.com/scalameta/scalafmt/pull/1968) ([kitbellew](https://github.com/kitbellew))
+- mdoc DefaultsModifier: allow multiple defaults [\#1967](https://github.com/scalameta/scalafmt/pull/1967) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: allow folding scaladoc to one line [\#1965](https://github.com/scalameta/scalafmt/pull/1965) ([kitbellew](https://github.com/kitbellew))
+
+## [v2.5.3](https://github.com/scalameta/scalafmt/tree/v2.5.3) (2020-05-11)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.5.2...v2.5.3)
+
+**Merged pull requests:**
+
+- \#1627 \[22\]: Router: preserve split after `=\>` in source=keep [\#1963](https://github.com/scalameta/scalafmt/pull/1963) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: shift lines after StateColumn align [\#1962](https://github.com/scalameta/scalafmt/pull/1962) ([kitbellew](https://github.com/kitbellew))
+
+## [v2.5.2](https://github.com/scalameta/scalafmt/tree/v2.5.2) (2020-05-09)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.5.1...v2.5.2)
+
+**Merged pull requests:**
+
+- interface ScalafmtSessionFactory shouldn't extend Scalafmt interface [\#1961](https://github.com/scalameta/scalafmt/pull/1961) ([unkarjedy](https://github.com/unkarjedy))
+- Documentation: retire Homebrew, point to coursier [\#1960](https://github.com/scalameta/scalafmt/pull/1960) ([kitbellew](https://github.com/kitbellew))
+- Documentation: old scalafmt plugin is deprecated [\#1959](https://github.com/scalameta/scalafmt/pull/1959) ([kitbellew](https://github.com/kitbellew))
+- RedundantBraces bugfix: skip deep if without else [\#1958](https://github.com/scalameta/scalafmt/pull/1958) ([kitbellew](https://github.com/kitbellew))
+- When using in pipeline don't print info to stderr [\#1957](https://github.com/scalameta/scalafmt/pull/1957) ([sideshowcoder](https://github.com/sideshowcoder))
+- RedundantBraces: don't rewrite anon infix funcs [\#1954](https://github.com/scalameta/scalafmt/pull/1954) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: use getAlignContainer to flush on comments [\#1951](https://github.com/scalameta/scalafmt/pull/1951) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: don't align `Defn` with `Block` as body [\#1947](https://github.com/scalameta/scalafmt/pull/1947) ([kitbellew](https://github.com/kitbellew))
+- Documentation for RedundantBraces settings [\#1945](https://github.com/scalameta/scalafmt/pull/1945) ([poslegm](https://github.com/poslegm))
+
+## [v2.5.1](https://github.com/scalameta/scalafmt/tree/v2.5.1) (2020-05-03)
+
+[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.5.0...v2.5.1)
+
+**Merged pull requests:**
+
+- Discord link fixed \(\#1942\) [\#1946](https://github.com/scalameta/scalafmt/pull/1946) ([poslegm](https://github.com/poslegm))
+- TreeOps: require space before `\(` in function [\#1944](https://github.com/scalameta/scalafmt/pull/1944) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: flush alignment blocks on blank line [\#1943](https://github.com/scalameta/scalafmt/pull/1943) ([kitbellew](https://github.com/kitbellew))
+- Changelog for 2.5.0 [\#1941](https://github.com/scalameta/scalafmt/pull/1941) ([poslegm](https://github.com/poslegm))
+- ScalafmtRunner: fix handling of trait "extends" [\#1940](https://github.com/scalameta/scalafmt/pull/1940) ([kitbellew](https://github.com/kitbellew))
+- ScalafmtConfig: see if dialect has trailing commas [\#1939](https://github.com/scalameta/scalafmt/pull/1939) ([kitbellew](https://github.com/kitbellew))
+- RedundantParens: may not be redundant around infix [\#1938](https://github.com/scalameta/scalafmt/pull/1938) ([kitbellew](https://github.com/kitbellew))
+- FormatWriter: simplify two regular expressions; fix pipe char bug [\#1936](https://github.com/scalameta/scalafmt/pull/1936) ([LeeTibbert](https://github.com/LeeTibbert))
+
## [v2.5.0](https://github.com/scalameta/scalafmt/tree/v2.5.0) (2020-04-30)
[Full Changelog](https://github.com/scalameta/scalafmt/compare/v2.5.0-RC3...v2.5.0)
diff --git a/docs/configuration.md b/docs/configuration.md
index 0643a5bb83..0c70b3d4a4 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -27,28 +27,6 @@ maxColumn
code on your phone.
- Consider refactoring your code before of choosing a value above 100.
-### `docstrings`
-
-```scala mdoc:defaults
-docstrings
-```
-
-```scala mdoc:scalafmt
-docstrings = ScalaDoc
----
-/** Align by second asterisk.
- *
- */
-```
-
-```scala mdoc:scalafmt
-docstrings = JavaDoc
----
-/** Align by first asterisk.
- *
- */
-```
-
### `assumeStandardLibraryStripMargin`
This parameter simply says the `.stripMargin` method was not redefined
@@ -57,9 +35,6 @@ the `|` character. Hence, that indentation can be modified.
```scala mdoc:defaults
assumeStandardLibraryStripMargin
-```
-
-```scala mdoc:defaults
align.stripMargin
```
@@ -139,7 +114,7 @@ including top-level.
binPack.preset = true
align.ifWhileOpenParen = false
continuationIndent.callSite = 4
- docstrings = JavaDoc
+ docstrings.style = Asterisk
importSelectors = binPack
newlines {
neverInResultType = true
@@ -210,9 +185,6 @@ class A(
```scala mdoc:defaults
continuationIndent.extendSite
-```
-
-```scala mdoc:defaults
continuationIndent.withSiteRelativeToExtends
```
@@ -568,6 +540,83 @@ or horizontally compact look.
Both settings attempt to play nice with other parameters, but some combinations
are prohibited and will result in an error.
+### Config-style formatting
+
+This formatting applies to argument lists in class definitions and method calls. It
+normally involves a newline after the opening parenthesis (or after the `implicit` keyword)
+and a newline before the closing parenthesis.
+
+As part of the formatting output, arguments are output one per line (but this is not used in
+determining whether the source uses config-style formatting).
+
+While this parameter is not technically under the `newlines` section, it
+logically belongs there.
+
+#### `optIn.configStyleArguments`
+
+If true, applies config-style formatting:
+
+- if single-line formatting is impossible
+- if the source uses config-style and `newlines.source = classic/keep`
+- if other parameters force config-style (see below)
+
+```scala mdoc:defaults
+optIn.configStyleArguments
+```
+
+```scala mdoc:scalafmt
+optIn.configStyleArguments = true
+maxColumn=45
+---
+object a {
+ // keeps single line
+ def method1(a: Int, b: String): Boolean
+
+ // forces config style
+ def method2(a: Int, b: String, c: String): Boolean
+
+ // preserves config style
+ def method3(
+ a: Int, b: String, c: String
+ ): Boolean
+}
+```
+
+#### Forcing config style
+
+Controls parameters which trigger forced config-style formatting.
+All conditions must be satisfied in order for this rule to apply.
+
+```scala mdoc:defaults
+runner.optimizer.forceConfigStyleOnOffset
+runner.optimizer.forceConfigStyleMinArgCount
+```
+
+- `runner.optimizer.forceConfigStyleOnOffset`: applies to method calls; if positive, specifies the
+ minimum character distance between the matching parentheses, excluding any whitespace
+- `runner.optimizer.forceConfigStyleMinArgCount` applies to method calls; specifies the minimum number of arguments
+
+```scala mdoc:scalafmt
+optIn.configStyleArguments = true
+runner.optimizer.forceConfigStyleOnOffset = 5
+runner.optimizer.forceConfigStyleMinArgCount = 2
+maxColumn = 60
+---
+object a {
+ // this is a definition, not a method call
+ def method(a: String, b: String = null): Boolean
+
+ // keeps single line; min offset not satisfied
+ method(a, b)
+
+ // keeps single line; min arg not satisfied
+ method(SomeVeryVeryVeryVeryLongArgument)
+
+ // forces config style
+ method(foo, bar)
+}
+```
+
### `danglingParentheses`
While this parameter is not technically under the `newlines` section, it
@@ -837,13 +886,20 @@ else {
### `newlines.afterCurlyLambda`
+This parameter controls handling of newlines after the arrow following the
+parameters of a curly brace lambda or partial function, and whether a space
+can be used for one-line formatting of the entire function body (if allowed
+but the body doesn't fit, a break is always forced).
+
```scala mdoc:defaults
newlines.afterCurlyLambda
```
```scala mdoc:scalafmt
-newlines.afterCurlyLambda = never
+newlines.afterCurlyLambda = squash
---
+// remove all blank lines if any
+// one-line formatting is allowed
something.map { x =>
@@ -855,8 +911,11 @@ something.map { x => f(x) }
```
```scala mdoc:scalafmt
-newlines.afterCurlyLambda = always
+newlines.afterCurlyLambda = never
---
+// remove all blank lines if any
+// one-line formatting depends on newlines.source:
+// yes for fold; no for unfold; otherwise, only if there was no break
something.map { x =>
@@ -870,6 +929,9 @@ something.map { x => f(x) }
```scala mdoc:scalafmt
newlines.afterCurlyLambda = preserve
---
+// if blank lines are present, keep only one
+// one-line formatting depends on newlines.source:
+// yes for fold; no for unfold; otherwise, only if there was no break
something.map { x =>
@@ -881,8 +943,10 @@ something.map { x => f(x) }
```
```scala mdoc:scalafmt
-newlines.afterCurlyLambda = squash
+newlines.afterCurlyLambda = always
---
+// ensure a single blank line
+// one-line formatting is not allowed
something.map { x =>
@@ -957,17 +1021,14 @@ newlines.implicitParamListModifierForce = [before,after]
def format(code: String, age: Int)(implicit ev: Parser, c: Context): String
```
-#### With `optIn.configStyleArguments`
+#### Implicit with `optIn.configStyleArguments`
-```scala mdoc:scalafmt
-maxColumn = 60
-optIn.configStyleArguments = true
-newlines.implicitParamListModifierForce = [after]
----
-def format(code: String, age: Int)(
- implicit ev: Parser, c: Context
-): String
-```
+While config-style normally requires a newline after the opening parenthesis, postponing
+that break until after the `implicit` keyword is allowed if other parameters require
+keeping this keyword attached to the opening brace.
+
+Therefore, any of the parameters described in this section will take precedence
+even when `optIn.configStyleArguments = true` is used.
### `newlines.afterInfix`
@@ -1027,6 +1088,56 @@ newlines.afterInfixBreakOnNested
If enabled, will force line breaks around a nested parenthesized
sub-expression in a multi-line infix expression.
+### `newlines.avoidForSimpleOverflow`
+
+A list parameter (of comma-separated flags), with possible flags described below.
+These flags relax formatting rules to allow occasional line overflow (i.e., when
+line exceeds `maxColumn`) in simple cases instead of introducing a newline.
+
+```scala mdoc:defaults
+newlines.avoidForSimpleOverflow
+```
+
+#### `newlines.avoidForSimpleOverflow=[tooLong]`
+
+> Since v2.6.0.
+
+This flag tries to avoid introducing a newline if the line would overflow even with a newline.
+
+```scala mdoc:scalafmt
+maxColumn = 50
+danglingParentheses.callSite = false
+newlines.avoidForSimpleOverflow = [tooLong]
+---
+object Example {
+ foo_bar_baz("the quick brown fox jumps over the lazy dog") {
+ println("")
+ }
+ foo_bar_baz("the quick brown fox jumps over a dog") {
+ println("")
+ }
+}
+```
+
+#### `newlines.avoidForSimpleOverflow=[punct]`
+
+> Since v2.6.0.
+
+This flag tries to avoid a newline if the line would overflow only because of
+trailing punctuation (non-alphanum symbols of length 1).
+
+With the flag set:
+
+```scala mdoc:scalafmt
+maxColumn = 80
+newlines.avoidForSimpleOverflow = [punct]
+---
+class Engine[TD, EI, PD, Q, P, A](
+ val dataSourceClassMap: Map[
+ String,
+ Class[_ <: BaseDataSource[TD, EI, Q, A]]]) {}
+```
+
## Rewrite Rules
To enable a rewrite rule, add it to the config like this
@@ -1186,7 +1297,8 @@ rewrite.redundantBraces.stringInterpolation = true
s"user id is ${id}"
```
-`rewrite.redundantBraces.parensForOneLineApply` is `true` by default for `edition` >= 2020-01. See also [newlines.afterCurlyLambda = squash](http://localhost:3000/scalafmt/docs/configuration.html#newlinesaftercurlylambda)
+`rewrite.redundantBraces.parensForOneLineApply` is `true` by default for `edition` >= 2020-01.
+See also [newlines.afterCurlyLambda = squash](#newlinesaftercurlylambda).
```scala mdoc:scalafmt
rewrite.rules = [RedundantBraces]
@@ -1576,6 +1688,191 @@ newlines.implicitParamListModifierForce = [before,after]
def format(code: String, age: Int)(implicit ev: Parser, c: Context): String
```
+## Comment processing
+
+### `comments`
+
+#### `comments.wrap`
+
+> Since v2.6.0.
+
+Allows wrapping comments exceeding `maxColumn`.
+
+```scala mdoc:defaults
+comments.wrap
+```
+
+##### `comments.wrap = standalone`
+
+A standalone comment is one which is surrounded by line breaks.
+
+```scala mdoc:scalafmt
+maxColumn = 20
+comments.wrap = standalone
+---
+/* long multiline comment */
+// long singleline comment
+val a = 1 // short
+val b = 2 // long singleline comment
+```
+
+##### `comments.wrap = trailing`
+
+A trailing comment is one which is followed by a line break.
+
+```scala mdoc:scalafmt
+maxColumn = 20
+comments.wrap = trailing
+---
+/* long multiline comment */
+// long singleline comment
+val a = 1 // short
+val b = 2 // long singleline comment
+```
+
+#### `comments.wrapStandaloneSlcAsSlc`
+
+> Since v2.6.0.
+
+This parameter allows formatting a standalone single-line comment (i.e., `//`)
+to be wrapped using the same type, not a multi-line comment (`/* ... */`).
+
+```scala mdoc:defaults
+comments.wrapStandaloneSlcAsSlc
+```
+
+```scala mdoc:scalafmt
+maxColumn = 20
+comments.wrap = trailing
+comments.wrapStandaloneSlcAsSlc = true
+---
+// long singleline comment
+val b = 2 // long singleline comment
+```
+
+### `docstrings`
+
+#### `docstrings.style`
+
+> Since v2.6.0.
+
+```scala mdoc:defaults
+docstrings.style
+```
+
+```scala mdoc:scalafmt
+docstrings.style = SpaceAsterisk
+---
+/** Format intermediate lines with a space and an asterisk,
+ * both below the two asterisks of the first line
+ */
+```
+
+```scala mdoc:scalafmt
+docstrings.style = Asterisk
+---
+/** Skip first line, format intermediate lines with an asterisk
+ * below the first asterisk of the first line (aka JavaDoc)
+ */
+```
+
+```scala mdoc:scalafmt
+docstrings.style = AsteriskSpace
+---
+/** Format intermediate lines with an asterisk and a space,
+ * both below the two asterisks of the first line
+ */
+```
+
+#### `docstrings.oneline`
+
+> Since v2.6.0.
+
+```scala mdoc:defaults
+docstrings.oneline
+```
+
+- `fold`
+
+```scala mdoc:scalafmt
+docstrings.style = Asterisk
+docstrings.oneline = fold
+---
+/** Scaladoc oneline */
+/**
+ * Scaladoc multiline
+ */
+val a = 1
+```
+
+- `unfold`
+
+```scala mdoc:scalafmt
+docstrings.style = Asterisk
+docstrings.oneline = unfold
+---
+/** Scaladoc oneline */
+/**
+ * Scaladoc multiline
+ */
+val a = 1
+```
+
+- `keep`
+
+```scala mdoc:scalafmt
+docstrings.style = Asterisk
+docstrings.oneline = keep
+---
+/** Scaladoc oneline */
+/**
+ * Scaladoc multiline
+ */
+val a = 1
+```
+
+#### `docstrings.wrap`
+
+Will parse scaladoc comments and reformat them.
+
+This functionality is generally limited to
+[standard scaladoc elements](https://docs.scala-lang.org/overviews/scaladoc/for-library-authors.html)
+and might lead to undesirable results in corner cases;
+for instance, the scaladoc parser doesn't have proper support of embedded HTML.
+
+However, [tables are supported](https://www.scala-lang.org/blog/2018/10/04/scaladoc-tables.html).
+
+> Since v2.6.0.
+
+```scala mdoc:defaults
+docstrings.wrap
+```
+
+```scala mdoc:scalafmt
+docstrings.wrap = yes
+maxColumn = 30
+---
+/**
+ * @param d the Double to square, meaning multiply by itself
+ * @return the result of squaring d
+ *
+ * Thus
+ * - if [[d]] represents a negative value:
+ * a. the result will be positive
+ * a. the value will be {{{d * d}}}
+ * a. it will be the same as for `-d`
+ * - however, if [[d]] is positive
+ * - the value will still be {{{d * d}}}
+ * - i.e., the same as {{{(-d) * (-d)}}}
+ *
+ * In other words:
+ * {{{
+ * res = d * d
+ * = (-d) * (-d) }}}
+ */
+def pow2(d: Double): Double
+```
+
## Disabling or customizing formatting
### For code block
@@ -1655,6 +1952,114 @@ will apply. It will also use different parameters for test suites.
> This parameter does not modify which files are formatted.
+## Spaces
+
+### `spaces.beforeContextBoundColon`
+
+```scala mdoc:defaults
+spaces.beforeContextBoundColon
+```
+
+```scala mdoc:scalafmt
+spaces.beforeContextBoundColon=Never
+---
+def method[A: Bound]: B
+def method[A : Bound]: B
+def method[A: Bound: Bound2]: B
+```
+
+```scala mdoc:scalafmt
+spaces.beforeContextBoundColon=Always
+---
+def method[A: Bound]: B
+def method[A : Bound]: B
+def method[A: Bound: Bound2]: B
+```
+
+```scala mdoc:scalafmt
+spaces.beforeContextBoundColon=IfMultipleBounds
+---
+def method[A: Bound]: B
+def method[A : Bound]: B
+def method[A: Bound: Bound2]: B
+```
+
+### `spaces.inImportCurlyBraces`
+
+```scala mdoc:defaults
+spaces.inImportCurlyBraces
+```
+
+```scala mdoc:scalafmt
+spaces.inImportCurlyBraces=true
+---
+import a.b.{c, d}
+```
+
+### `spaces.inParentheses`
+
+```scala mdoc:defaults
+spaces.inParentheses
+```
+
+```scala mdoc:scalafmt
+spaces.inParentheses=true
+---
+foo(a, b)
+```
+
+### `spaces.neverAroundInfixTypes`
+
+```scala mdoc:defaults
+spaces.neverAroundInfixTypes
+```
+
+```scala mdoc:scalafmt
+spaces.neverAroundInfixTypes=["##"]
+---
+def f: Foo##Repr
+def g: Foo\/Repr
+// usage same operator not as type
+def e = a##b
+```
+
+### `spaces.afterKeywordBeforeParen`
+
+```scala mdoc:defaults
+spaces.afterKeywordBeforeParen
+```
+
+```scala mdoc:scalafmt
+spaces.afterKeywordBeforeParen = false
+---
+if (a) println("HELLO!")
+while (a) println("HELLO!")
+```
+
+### `spaces.inByNameTypes`
+
+```scala mdoc:defaults
+spaces.inByNameTypes
+```
+
+```scala mdoc:scalafmt
+spaces.inByNameTypes = false
+---
+def foo(a: => A): A
+```
+
+### `spaces.afterSymbolicDefs`
+
+```scala mdoc:defaults
+spaces.afterSymbolicDefs
+```
+
+```scala mdoc:scalafmt
+spaces.afterSymbolicDefs=true
+---
+def +++(a: A): F[A]
+```
+
## Literals
> Since v2.5.0.
@@ -1764,7 +2169,49 @@ literals.float=Lower
10e-1f
```
-## Miscellaneous
+## XML
+
+Controls formatting of Scala embedded within XML.
+
+### `xmlLiterals.assumeFormatted`
+
+> Since v2.6.0.
+
+If set, formats embedded Scala relative to containing XML, making the assumption
+that XML itself is properly formatted. Otherwise, formatting is relative to the
+outer Scala code which contains the XML literals.
+
+```scala mdoc:defaults
+xmlLiterals.assumeFormatted
+```
+
+```scala mdoc:scalafmt
+maxColumn = 40
+xmlLiterals.assumeFormatted = true
+---
+object Example2 {
+ def apply() = {
+
+ { (1 + 2 + 3).toString("some long format") }
+
+ }
+}
+```
+
+```scala mdoc:scalafmt
+maxColumn = 40
+xmlLiterals.assumeFormatted = false
+---
+object Example2 {
+ def apply() = {
+
+ { (1 + 2 + 3).toString("some long format") }
+
+ }
+}
+```
+
+## Binpacking
### `binPack.literalArgumentLists`
@@ -1801,8 +2248,84 @@ See also:
- all other `binPack.literalXXX` parameters (see list at the bottom) are
self-explanatory.
+### `binPack.parentConstructors`
+
+Parent constructors are `C` and `D` in `class A extends B with C and D`.
+Changed from a boolean to a wider set of options in v2.6.0.
+
+```scala mdoc:defaults
+binPack.parentConstructors
+```
+
+> Keep in mind that explicitly specifying the default value might change
+> behaviour; other parameters, such as [`newlines.source`](#newlinessource),
+> could interpret implied default differently but yield to an explicit value.
+
+```scala mdoc:scalafmt
+binPack.parentConstructors = Always
+maxColumn = 30
+---
+object A {
+ trait Foo
+ extends Bar
+ with Baz
+}
+```
+
+```scala mdoc:scalafmt
+binPack.parentConstructors = Never
+maxColumn = 30
+---
+object A {
+ trait Foo extends Bar with Baz
+}
+```
+
+```scala mdoc:scalafmt
+binPack.parentConstructors = Oneline
+maxColumn = 30
+---
+object A {
+ class Foo(a: Int)
+ extends Bar
+ with Baz
+
+ class Foo(
+ a: Int
+ )
+ extends Bar
+ with Baz
+}
+```
+
+```scala mdoc:scalafmt
+binPack.parentConstructors = OnelineIfPrimaryOneline
+maxColumn = 30
+---
+object A {
+ class Foo(a: Int, b: Int)
+ extends Bar
+ with Baz
+
+ class Foo(
+ a: Int,
+ b: Int
+ ) extends Bar with Baz
+}
+```
+
+## Classic select chains
+
+The parameters below control formatting of select chains when `newlines.source = classic`,
+and specifically which select expressions are included in a chain.
+
+Generally, a chain can either be formatted on one line up to the last select, or will
+have a break on the first select.
+
### `includeCurlyBraceInSelectChains`
+Controls if select followed by curly braces can _start_ a chain.
+
```scala mdoc:defaults
includeCurlyBraceInSelectChains
```
@@ -1828,6 +2351,8 @@ List(1)
### `includeNoParensInSelectChains`
+Controls if select _not_ followed by an apply can _start_ a chain.
+
```scala mdoc:defaults
includeNoParensInSelectChains
```
@@ -1848,6 +2373,9 @@ List(1).toIterator.buffered.map(_ + 2).filter(_ > 2)
### `optIn.breakChainOnFirstMethodDot`
+Keeps the break on the first select of the chain if the source contained one.
+Has no effect if there was no newline in the source.
+
```scala mdoc:defaults
optIn.breakChainOnFirstMethodDot
```
@@ -1864,11 +2392,84 @@ foo
```scala mdoc:scalafmt
optIn.breakChainOnFirstMethodDot = true
---
+// preserve break on first dot and break on subsequent dots
foo
- .map(_ + 1) // preserve existing newlines
+ .map(_ + 1).filter(_ > 2)
+```
+
+### `optIn.breaksInsideChains`
+
+Controls whether to preserve a newline before each subsequent select when the
+very first one used a line break; that is, this parameter doesn't prohibit
+single-line formatting even if there are source breaks down the chain.
+
+If false, each subsequent select within the chain will behave exactly like the first,
+that is, either the entire chain will be formatted on one line, or will contain a break
+on every select.
+
+If true, preserves existence or lack of breaks on subsequent selects if the first
+select was formatted with a newline.
+
+```scala mdoc:defaults
+optIn.breaksInsideChains
+```
+
+```scala mdoc:scalafmt
+optIn.breaksInsideChains = true
+maxColumn = 35
+---
+foo.bar(_ + 1)
+ .baz(_ > 2).qux
+foo.bar(_ + 1).baz(_ > 2).qux
+foo.bar(_ + 1).baz(_ > 2).qux(_ * 12)
+foo.bar(_ + 1).baz(_ > 2).qux { _ * 12 }
+foo.bar(_ + 1)
+ .baz(_ > 2).qux(_ * 12)
+```
+
+```scala mdoc:scalafmt
+optIn.breaksInsideChains = false
+maxColumn = 35
+---
+foo.bar(_ + 1)
+ .baz(_ > 2).qux
+foo.bar(_ + 1).baz(_ > 2).qux
+foo.bar(_ + 1).baz(_ > 2).qux(_ * 12)
+foo.bar(_ + 1).baz(_ > 2).qux { _ * 12 }
+foo.bar(_ + 1)
+ .baz(_ > 2).qux(_ * 12)
+```
+
+### `optIn.encloseClassicChains`
+
+Controls what happens if a chain enclosed in parentheses is followed by
+additional selects. Those additional selects will be considered part of
+the enclosed chain if and only if this flag is false.
+
+> Since 2.6.2.
+
+```scala mdoc:defaults
+optIn.encloseClassicChains
+```
+
+```scala mdoc:scalafmt
+optIn.encloseClassicChains = true
+maxColumn = 30
+---
+(foo.map(_ + 1).map(_ + 1))
+ .filter(_ > 2)
+```
+
+```scala mdoc:scalafmt
+optIn.encloseClassicChains = false
+maxColumn = 30
+---
+(foo.map(_ + 1).map(_ + 1))
.filter(_ > 2)
```
+## Miscellaneous
+
### `optIn.forceBlankLineBeforeDocstring`
If true, always insert a blank line before docstrings;
diff --git a/docs/installation.md b/docs/installation.md
index 315017f035..8e900336e2 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -118,7 +118,7 @@ in the `.scalafmt.conf` configuration file and downloaded dynamically.
```scala
// In project/plugins.sbt. Note, does not support sbt 0.13, only sbt 1.x.
-addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.2") // "2.3.2" is just sbt plugin version
+addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") // "2.4.0" is just sbt plugin version
```
[](https://maven-badges.herokuapp.com/maven-central/org.scalameta/sbt-scalafmt)
@@ -199,6 +199,16 @@ object MyScalafmtPlugin extends AutoPlugin {
include ".scalafmt-common.conf"
```
+### Limit parallelism
+
+You can limit formatting parallelism for projects with multiple subprojects in your `build.sbt`:
+
+```scala
+import org.scalafmt.sbt.ConcurrentRestrictionTags
+
+Global / concurrentRestrictions += Tags.limit(org.scalafmt.sbt.ConcurrentRestrictionTags.Scalafmt, 4)
+```
+
## CLI
The recommended way to install the scalafmt command line tool is with
@@ -245,8 +255,12 @@ coursier bootstrap org.scalameta:scalafmt-cli_2.13:@STABLE_VERSION@ \
./scalafmt --version # should be @STABLE_VERSION@
```
-It is **recommended** to put this bootstrap script in your code repository to
-make sure everyone on your team, as well as CI, uses the same scalafmt version.
+If a `version` is defined in `.scalafmt.conf`, the CLI binary will honor it
+by automatically resolving and downloading the corresponding artifacts if it
+does not match its own version. Otherwise, it is **recommended** to put this
+bootstrap script in your code repository to make sure everyone on your team,
+as well as CI, uses the same scalafmt version.
+
To configure which files to format, see [project](configuration.md#project).
To customize the JVM options, use the Coursier option `--java-opt`, more info
@@ -287,8 +301,8 @@ scalafmt-native --help # should show version @STABLE_VERSION@
```
> The native image binaries have the limitation of working only with one version
-> of Scalafmt. > The native binaries fail when the `version` setting in
-> `.scalafmt.conf` does not match the version of the native binary. > It's
+> of Scalafmt. The native binaries fail when the `version` setting in
+> `.scalafmt.conf` does not match the version of the native binary. It's
> recommended to use the JVM binary if you expect to use Scalafmt in multiple
> projects with different Scalafmt versions.
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index db55a4b84c..eaa8831b7c 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -6,8 +6,8 @@ import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._
object Dependencies {
val metaconfigV = "0.9.10"
- val scalametaV = "4.3.10"
- val scalatestV = "3.1.1"
+ val scalametaV = "4.3.19"
+ val scalatestV = "3.2.0"
val scalacheckV = "1.14.3"
val coursier = "1.0.3"
@@ -20,8 +20,8 @@ object Dependencies {
val scalametaTestkit = "org.scalameta" %% "testkit" % scalametaV
- val scalacheck = "org.scalacheck" %% "scalacheck" % scalacheckV
- val scalatest = Def.setting("org.scalatest" %%% "scalatest" % scalatestV)
+ val scalacheck = "org.scalacheck" %% "scalacheck" % scalacheckV
+ val scalatest = Def.setting("org.scalatest" %%% "scalatest" % scalatestV)
val scalameta = Def.setting {
scalaBinaryVersion.value match {
case "2.11" =>
@@ -38,8 +38,8 @@ object Dependencies {
"org.scalameta" %%% "scalameta" % scalametaV excludeAll scalapb.value
}
}
- val metaconfig = Def.setting("com.geirsson" %%% "metaconfig-core" % metaconfigV)
+ val metaconfig = Def.setting("com.geirsson" %%% "metaconfig-core" % metaconfigV)
val metaconfigTypesafe = Def.setting("com.geirsson" %%% "metaconfig-typesafe-config" % metaconfigV)
- val metaconfigHocon = Def.setting("com.geirsson" %%% "metaconfig-hocon" % metaconfigV)
+ val metaconfigHocon = Def.setting("com.geirsson" %%% "metaconfig-hocon" % metaconfigV)
}
diff --git a/project/build.properties b/project/build.properties
index 797e7ccfdb..0837f7a132 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=1.3.10
+sbt.version=1.3.13
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 632c81582a..784aaba8ff 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -4,16 +4,16 @@ resolvers ++= Seq(
Resolver.bintrayIvyRepo("jetbrains", "sbt-plugins")
)
-addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.1.5")
-addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.4")
+addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.3")
+addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0")
addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0")
addSbtPlugin(
"io.get-coursier" % "sbt-coursier" % coursier.util.Properties.version
)
-addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
-addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.1")
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.0")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.7.0")
-addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.0")
+addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.3")
diff --git a/scalafmt b/scalafmt
index 4c1d50d3a5..a50dc07054 100755
Binary files a/scalafmt and b/scalafmt differ
diff --git a/scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties b/scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties
new file mode 100644
index 0000000000..6883e8f348
--- /dev/null
+++ b/scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties
@@ -0,0 +1,13 @@
+Args=--no-server \
+ -Dscalafmt.native-image=true \
+ --no-fallback \
+ --enable-http \
+ --enable-https \
+ --enable-all-security-services \
+ --install-exit-handlers \
+ --initialize-at-build-time \
+ --verbose \
+ -H:EnableURLProtocols=http,https \
+ -H:+TraceClassInitialization \
+ -H:+RemoveSaturatedTypeFlows \
+ -H:+ReportExceptionStackTraces
diff --git a/scalafmt-cli/src/main/graal/reflection.json b/scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/reflection.json
similarity index 100%
rename from scalafmt-cli/src/main/graal/reflection.json
rename to scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/reflection.json
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala
index 73bfaf6e7e..057d216aba 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala
@@ -105,6 +105,11 @@ object CliArgParser {
.text(
"file or directory, when missing all *.scala files are formatted."
)
+ opt[Unit]("respect-project-filters")
+ .action((_, c) => c.copy(respectProjectFilters = true))
+ .text(
+ "use project filters even when specific files to format are provided"
+ )
opt[String]('c', "config")
.action(readConfigFromFile)
.text("a file path to .scalafmt.conf.")
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliOptions.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliOptions.scala
index b2020e6b76..d8dfa6a106 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliOptions.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliOptions.scala
@@ -112,6 +112,7 @@ case class CliOptions(
range: Set[Range] = Set.empty[Range],
customFiles: Seq[AbsoluteFile] = Nil,
customExcludes: Seq[String] = Nil,
+ respectProjectFilters: Boolean = false,
git: Option[Boolean] = None,
nonInteractive: Boolean = false,
mode: Option[FileFetchMode] = None,
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/InputMethod.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/InputMethod.scala
index a6d165804e..7964e90794 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/InputMethod.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/InputMethod.scala
@@ -10,8 +10,6 @@ import org.scalafmt.util.AbsoluteFile
import org.scalafmt.util.FileOps
sealed abstract class InputMethod {
- def isSbt: Boolean = filename.endsWith(".sbt")
- def isSc: Boolean = filename.endsWith(".sc")
def readInput(options: CliOptions): String
def filename: String
def write(formatted: String, original: String, options: CliOptions): ExitCode
@@ -76,10 +74,9 @@ object InputMethod {
original: String,
revised: String
): String = {
- import collection.JavaConverters._
+ import org.scalafmt.CompatCollections.JavaConverters._
def jList(string: String) =
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- java.util.Collections.list(augmentString(string).lines.asJavaEnumeration)
+ java.util.Collections.list(string.linesIterator.asJavaEnumeration)
val a = jList(original)
val b = jList(revised)
val diff = difflib.DiffUtils.diff(a, b)
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala
index dcfb137e9e..2937d8f6b5 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala
@@ -50,7 +50,7 @@ class Scalafmt210 {
if (filename.endsWith(".sbt")) SRunner.sbt
else SRunner.default
val style = scalafmtStyle.copy(runner = runner)
- Scalafmt.formatCode(code, style, filename = filename) match {
+ Scalafmt.formatCode(code, style, filename = filename).formatted match {
case Formatted.Success(formattedCode) => formattedCode
case error =>
error match {
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala
index 2e67de1337..3cff9dbbda 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala
@@ -8,7 +8,7 @@ import metaconfig.Configured
import org.scalafmt.Error.{MisformattedFile, NoMatchingFiles}
import org.scalafmt.{Formatted, Scalafmt, Versions}
import org.scalafmt.config.{FilterMatcher, ScalafmtConfig}
-import org.scalafmt.CompatParCollections.Converters._
+import org.scalafmt.CompatCollections.ParConverters._
import org.scalafmt.util.OsSpecific
import scala.meta.internal.tokenizers.PlatformTokenizerCache
@@ -78,19 +78,16 @@ object ScalafmtCoreRunner extends ScalafmtRunner {
private[this] def unsafeHandleFile(
inputMethod: InputMethod,
options: CliOptions,
- config: ScalafmtConfig
+ scalafmtConfig: ScalafmtConfig
): ExitCode = {
val input = inputMethod.readInput(options)
- val scalafmtConfig =
- if (inputMethod.isSbt || inputMethod.isSc) config.forSbt
- else config
val formatResult = Scalafmt.formatCode(
input,
scalafmtConfig,
options.range,
inputMethod.filename
)
- formatResult match {
+ formatResult.formatted match {
case Formatted.Success(formatted) =>
inputMethod.write(formatted, input, options)
case Formatted.Failure(e) =>
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala
index 2ac3e1cf22..797e6382b1 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala
@@ -6,8 +6,9 @@ import java.nio.file.Paths
import java.util.concurrent.atomic.{AtomicInteger, AtomicReference}
import java.util.function.UnaryOperator
-import org.scalafmt.CompatParCollections.Converters._
+import org.scalafmt.CompatCollections.ParConverters._
import org.scalafmt.Error.{MisformattedFile, NoMatchingFiles}
+import org.scalafmt.dynamic.ScalafmtDynamicError
import org.scalafmt.interfaces.Scalafmt
import org.scalafmt.interfaces.ScalafmtSession
import org.scalafmt.interfaces.ScalafmtSessionFactory
@@ -27,20 +28,27 @@ object ScalafmtDynamicRunner extends ScalafmtRunner {
.withReporter(reporter)
.withRespectProjectFilters(false)
- val session = scalafmtInstance match {
- case t: ScalafmtSessionFactory =>
- val session = t.createSession(options.configPath)
- if (session == null) return reporter.getExitCode // XXX: returning
- session
- case _ => new MyInstanceSession(options, scalafmtInstance)
- }
+ val session =
+ try {
+ scalafmtInstance match {
+ case t: ScalafmtSessionFactory =>
+ t.createSession(options.configPath)
+ case _ => new MyInstanceSession(options, scalafmtInstance)
+ }
+ } catch {
+ case _: ScalafmtDynamicError.ConfigError =>
+ return reporter.getExitCode // XXX: returning
+ }
- val customMatcher = getFileMatcher(options.customFiles)
- val inputMethods = getInputMethods(
- options,
- (x: AbsoluteFile) =>
- customMatcher(x) && session.matchesProjectFilters(x.jfile.toPath)
- )
+ def sessionMatcher(x: AbsoluteFile): Boolean =
+ session.matchesProjectFilters(x.jfile.toPath)
+ val filterMatcher: AbsoluteFile => Boolean =
+ if (options.customFiles.isEmpty) sessionMatcher
+ else {
+ val customMatcher = getFileMatcher(options.customFiles)
+ x => customMatcher(x) && sessionMatcher(x)
+ }
+ val inputMethods = getInputMethods(options, filterMatcher)
if (inputMethods.isEmpty && options.mode.isEmpty && !options.stdIn)
throw NoMatchingFiles
@@ -76,10 +84,15 @@ object ScalafmtDynamicRunner extends ScalafmtRunner {
private final class MyInstanceSession(opts: CliOptions, instance: Scalafmt)
extends ScalafmtSession {
- private val customFiles = opts.customFiles.map(_.jfile.toPath)
+ // check config first
+ instance.format(opts.configPath, opts.configPath, "")
+ private val customFiles =
+ if (opts.respectProjectFilters) Seq.empty
+ else opts.customFiles.filter(_.jfile.isFile).map(_.jfile.toPath)
override def format(file: Path, code: String): String = {
// DESNOTE(2017-05-19, pjrt): A plain, fully passed file will (try to) be
// formatted regardless of what it is or where it is.
+ // NB: Unless respectProjectFilters is also specified.
val formatter =
if (customFiles.contains(file)) instance
else instance.withRespectProjectFilters(true)
@@ -106,21 +119,19 @@ object ScalafmtDynamicRunner extends ScalafmtRunner {
private def getFileMatcher(
paths: Seq[AbsoluteFile]
): AbsoluteFile => Boolean = {
- if (paths.isEmpty) _ => true
- else {
- val (files, dirs) = paths.partition(_.jfile.isFile)
- (x: AbsoluteFile) =>
- files.contains(x) || {
- val filename = x.path
- dirs.exists { dir =>
- val dirname = dir.path
- filename.startsWith(dirname) && (
- filename.length == dirname.length ||
- filename.charAt(dirname.length) == File.separatorChar
- )
- }
+ require(paths.nonEmpty)
+ val (files, dirs) = paths.partition(_.jfile.isFile)
+ (x: AbsoluteFile) =>
+ files.contains(x) || {
+ val filename = x.path
+ dirs.exists { dir =>
+ val dirname = dir.path
+ filename.startsWith(dirname) && (
+ filename.length == dirname.length ||
+ filename.charAt(dirname.length) == File.separatorChar
+ )
}
- }
+ }
}
}
diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala
index 154cfa6046..c220d44788 100644
--- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala
+++ b/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala
@@ -57,6 +57,8 @@ trait ScalafmtRunner {
case d if d.jfile.isDirectory => fetchFiles(d).filter(canFormat)
// DESNOTE(2017-05-19, pjrt): A plain, fully passed file will (try to) be
// formatted regardless of what it is or where it is.
+ // NB: Unless respectProjectFilters is also specified.
+ case f if options.respectProjectFilters && !canFormat(f) => Seq.empty
case f => Seq(f)
}
diff --git a/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatCollections.scala b/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatCollections.scala
new file mode 100644
index 0000000000..84094035db
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatCollections.scala
@@ -0,0 +1,6 @@
+package org.scalafmt
+
+private[scalafmt] object CompatCollections {
+ val JavaConverters = scala.collection.JavaConverters
+ object ParConverters
+}
diff --git a/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatParCollections.scala b/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatParCollections.scala
deleted file mode 100644
index 547badb175..0000000000
--- a/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatParCollections.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.scalafmt
-
-private[scalafmt] object CompatParCollections {
- val Converters = {
- CollectionConverters
- }
-
- object CollectionConverters
-}
diff --git a/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatCollections.scala b/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatCollections.scala
new file mode 100644
index 0000000000..84094035db
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatCollections.scala
@@ -0,0 +1,6 @@
+package org.scalafmt
+
+private[scalafmt] object CompatCollections {
+ val JavaConverters = scala.collection.JavaConverters
+ object ParConverters
+}
diff --git a/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatParCollections.scala b/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatParCollections.scala
deleted file mode 100644
index 547badb175..0000000000
--- a/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatParCollections.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.scalafmt
-
-private[scalafmt] object CompatParCollections {
- val Converters = {
- CollectionConverters
- }
-
- object CollectionConverters
-}
diff --git a/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatCollections.scala b/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatCollections.scala
new file mode 100644
index 0000000000..392588673e
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatCollections.scala
@@ -0,0 +1,6 @@
+package org.scalafmt
+
+private[scalafmt] object CompatCollections {
+ val JavaConverters = scala.jdk.CollectionConverters
+ val ParConverters = scala.collection.parallel.CollectionConverters
+}
diff --git a/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatParCollections.scala b/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatParCollections.scala
deleted file mode 100644
index f528c6ccb6..0000000000
--- a/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatParCollections.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.scalafmt
-
-private[scalafmt] object CompatParCollections {
- val Converters = {
- scala.collection.parallel.CollectionConverters
- }
-}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/Formatted.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/Formatted.scala
index 41845e4208..7c821d8805 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/Formatted.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/Formatted.scala
@@ -1,5 +1,7 @@
package org.scalafmt
+import org.scalafmt.config.ScalafmtConfig
+
sealed abstract class Formatted {
def toEither: Either[Throwable, String] =
@@ -18,4 +20,12 @@ sealed abstract class Formatted {
object Formatted {
case class Success(formattedCode: String) extends Formatted
case class Failure(e: Throwable) extends Formatted
+
+ private[scalafmt] case class Result(
+ formatted: Formatted,
+ config: ScalafmtConfig
+ ) {
+ def get: String = formatted.get
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala
index 0c4b529d80..faad57647c 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala
@@ -3,7 +3,10 @@ package org.scalafmt
import metaconfig.Configured
import scala.meta.Dialect
import scala.meta.inputs.Input
-import scala.util.control.NonFatal
+import scala.util.Failure
+import scala.util.Success
+import scala.util.Try
+
import org.scalafmt.config.Config
import org.scalafmt.Error.PreciseIncomplete
import org.scalafmt.config.FormatEvent.CreateFormatOps
@@ -46,7 +49,7 @@ object Scalafmt {
range: Set[Range],
filename: String
): Formatted = {
- formatCode(code, style, range, filename)
+ formatCode(code, style, range, filename).formatted
}
private[scalafmt] def formatCode(
@@ -54,13 +57,17 @@ object Scalafmt {
baseStyle: ScalafmtConfig = ScalafmtConfig.default,
range: Set[Range] = Set.empty,
filename: String = defaultFilename
- ): Formatted = {
+ ): Formatted.Result = {
val style =
if (filename == defaultFilename) baseStyle
- else baseStyle.getConfigFor(filename) // might throw for invalid conf
+ else { // might throw for invalid conf
+ val style = baseStyle.getConfigFor(filename)
+ val isSbt = filename.endsWith(".sc") || filename.endsWith(".sbt")
+ if (isSbt) style.forSbt else style
+ }
val runner = style.runner
- try {
- if (code.matches("\\s*")) Formatted.Success(System.lineSeparator())
+ Try {
+ if (code.matches("\\s*")) System.lineSeparator()
else {
val isWindows = containsWindowsLineEndings(code)
val unixCode = if (isWindows) {
@@ -70,7 +77,7 @@ object Scalafmt {
}
val toParse = Rewrite(Input.VirtualFile(filename, unixCode), style)
val tree = runner.parse(toParse).get
- val formatOps = new FormatOps(tree, style)
+ val formatOps = new FormatOps(tree, style, filename)
runner.event(CreateFormatOps(formatOps))
val formatWriter = new FormatWriter(formatOps)
val partial = BestFirstSearch(formatOps, range, formatWriter)
@@ -85,15 +92,15 @@ object Scalafmt {
formattedString
}
if (partial.reachedEOF) {
- Formatted.Success(correctedFormattedString)
+ correctedFormattedString
} else {
val pos = formatOps.tokens(partial.state.depth).left.pos
throw PreciseIncomplete(pos, correctedFormattedString)
}
}
- } catch {
- // TODO(olafur) add more fine grained errors.
- case NonFatal(e) => Formatted.Failure(e)
+ } match {
+ case Failure(e) => Formatted.Result(Formatted.Failure(e), style)
+ case Success(s) => Formatted.Result(Formatted.Success(s), style)
}
}
@@ -103,7 +110,7 @@ object Scalafmt {
style: ScalafmtConfig = ScalafmtConfig.default,
range: Set[Range] = Set.empty[Range]
): Formatted = {
- formatCode(code, style, range)
+ formatCode(code, style, range).formatted
}
def parseHoconConfig(configString: String): Configured[ScalafmtConfig] =
@@ -115,7 +122,7 @@ object Scalafmt {
/** Utility method to change dialect on ScalafmtConfig.
*
* Binary compatibility is guaranteed between releases, unlike with ScalafmtConfig.copy.
- **/
+ */
def configWithDialect(
config: ScalafmtConfig,
dialect: Dialect
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala
index a8ebe138d0..b13477bb8b 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Align.scala
@@ -4,7 +4,6 @@ import metaconfig._
import metaconfig.generic.Surface
/**
- *
* @param openParenCallSite
* If true AND bin-packing is true, then call-site
* arguments won't be aligned by the opening
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/BinPack.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/BinPack.scala
index e7cb769742..069b577994 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/BinPack.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/BinPack.scala
@@ -3,7 +3,6 @@ package org.scalafmt.config
import metaconfig._
/**
- *
* @param unsafeCallSite DO NOT USE! This option is buggy for complicated expressions.
* The only reason this option exists is to support
* the [[literalArgumentLists]] option, which enables callSite
@@ -23,16 +22,15 @@ import metaconfig._
* or "Byte".
* @param literalsExclude Regexes for literal to exclude from [[literalArgumentLists]].
* @param parentConstructors Parent constructors are C and D in
- * "class A extends B with C and D". If true,
+ * "class A extends B with C and D". If "Always",
* scalafmt will fit as many parent constructors
- * on a single line. If false, each parent
+ * on a single line. If "Never", each parent
* constructor gets its own line.
- *
*/
case class BinPack(
unsafeCallSite: Boolean = false,
unsafeDefnSite: Boolean = false,
- parentConstructors: Boolean = false,
+ parentConstructors: BinPack.ParentCtors = BinPack.ParentCtors.MaybeNever,
literalArgumentLists: Boolean = true,
literalsIncludeSimpleExpr: Boolean = false,
literalsSingleLine: Boolean = false,
@@ -53,11 +51,46 @@ object BinPack {
val enabled = BinPack(
unsafeDefnSite = true,
unsafeCallSite = true,
- parentConstructors = true
+ parentConstructors = ParentCtors.Always
)
implicit val preset: PartialFunction[Conf, BinPack] = {
case Conf.Bool(true) => enabled
case Conf.Bool(false) => BinPack()
}
+ sealed abstract class ParentCtors
+ object ParentCtors {
+ case object Always extends ParentCtors
+ case object Never extends ParentCtors
+ case object Oneline extends ParentCtors
+ case object OnelineIfPrimaryOneline extends ParentCtors
+
+ val oneOfReader: ConfCodec[ParentCtors] = ReaderUtil.oneOf(
+ Always,
+ Never,
+ Oneline,
+ OnelineIfPrimaryOneline
+ )
+
+ /* don't expose this; it will serve as unspecified, default to Never but
+ * could be overridden by other parameters, such as newlines.source */
+ case object MaybeNever extends ParentCtors
+
+ implicit val encoder: ConfEncoder[ParentCtors] =
+ new ConfEncoder[ParentCtors] {
+ override def write(value: ParentCtors): Conf =
+ if (value eq MaybeNever) Conf.Str("never")
+ else oneOfReader.write(value)
+ }
+ implicit val decoder: ConfDecoder[ParentCtors] =
+ new ConfDecoder[ParentCtors] {
+ override def read(conf: Conf): Configured[ParentCtors] =
+ conf match {
+ case Conf.Bool(true) => Configured.ok(Always)
+ case Conf.Bool(false) => Configured.ok(Never)
+ case _ => oneOfReader.read(conf)
+ }
+ }
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Comments.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Comments.scala
new file mode 100644
index 0000000000..734e69c507
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Comments.scala
@@ -0,0 +1,40 @@
+package org.scalafmt.config
+
+import metaconfig._
+
+/**
+ * @param wrap
+ * defines whether to wrap comments; the comment to be wrapped may not
+ * contain nested comments.
+ * - no: do not wrap
+ * - trailing: wrap the last comment on a line (line break after)
+ * - standalone: wrap standalone comments (line break both before
+ * and after the comment)
+ * @param wrapStandaloneSlcAsSlc
+ * if `wrap` is enabled, wrap standalone single-line comments (//) using
+ * the same type, rather than multi-line comments (/* ... */); it won't
+ * be applied to trailing comments as indentation would be inconsistent.
+ */
+case class Comments(
+ wrap: Comments.Wrap = Comments.Wrap.no,
+ wrapStandaloneSlcAsSlc: Boolean = false
+) {
+ implicit lazy val decoder = generic.deriveDecoder(this).noTypos
+}
+
+object Comments {
+
+ implicit val surface: generic.Surface[Comments] =
+ generic.deriveSurface[Comments]
+ implicit val encoder = generic.deriveEncoder[Comments]
+
+ sealed abstract class Wrap
+ object Wrap {
+ case object no extends Wrap
+ case object standalone extends Wrap
+ case object trailing extends Wrap
+ implicit val reader: ConfCodec[Wrap] =
+ ReaderUtil.oneOf[Wrap](no, standalone, trailing)
+ }
+
+}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Docstrings.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Docstrings.scala
index a6ad9fd924..865b5950ea 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Docstrings.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Docstrings.scala
@@ -1,13 +1,91 @@
package org.scalafmt.config
-import metaconfig.ConfCodec
+import metaconfig._
-sealed abstract class Docstrings
+/**
+ * @param oneline
+ * - if fold, try to fold short docstrings into a single line
+ * - if unfold, unfold a single-line docstring into multiple lines
+ * - if keep, preserve the current formatting
+ * @param wrap
+ * if yes, allow reformatting/rewrapping the contents of the docstring
+ * @param style
+ * - Asterisk: format intermediate lines with an asterisk below the
+ * first asterisk of the first line (aka JavaDoc)
+ * - SpaceAsterisk: format intermediate lines with a space and
+ * an asterisk, both below the two asterisks of the first line
+ * - AsteriskSpace: format intermediate lines with an asterisk
+ * and a space, both below the two asterisks of the first line
+ */
+case class Docstrings(
+ oneline: Docstrings.Oneline = Docstrings.Oneline.keep,
+ wrap: Docstrings.Wrap = Docstrings.Wrap.no,
+ style: Option[Docstrings.Style] = Some(Docstrings.SpaceAsterisk)
+) {
+ import Docstrings._
+
+ @inline
+ def skipFirstLine: Boolean = style.exists(_.skipFirstLine)
+ def isSpaceAsterisk: Boolean = style.contains(SpaceAsterisk)
+ def isAsteriskSpace: Boolean = style.contains(AsteriskSpace)
+
+ implicit lazy val decoder: ConfDecoder[Docstrings] = {
+ val genericDecoder = generic.deriveDecoder(this).noTypos
+ new ConfDecoder[Docstrings] {
+ override def read(conf: Conf): Configured[Docstrings] =
+ conf match {
+ case Conf.Str("preserve") =>
+ Configured.ok(copy(style = None))
+ case Conf.Str("ScalaDoc") =>
+ Configured.ok(copy(style = Some(SpaceAsterisk)))
+ case Conf.Str("JavaDoc") =>
+ Configured.ok(copy(style = Some(Asterisk)))
+ case _: Conf.Str =>
+ reader.read(conf).map(x => copy(style = Some(x)))
+ case _ => genericDecoder.read(conf)
+ }
+ }
+ }
+
+}
object Docstrings {
- implicit val reader: ConfCodec[Docstrings] =
- ReaderUtil.oneOf[Docstrings](JavaDoc, ScalaDoc, preserve)
- case object JavaDoc extends Docstrings
- case object ScalaDoc extends Docstrings
- case object preserve extends Docstrings
+
+ implicit val surface: generic.Surface[Docstrings] =
+ generic.deriveSurface[Docstrings]
+ implicit val encoder = generic.deriveEncoder[Docstrings]
+
+ sealed abstract class Style {
+ def skipFirstLine: Boolean
+ }
+ case object Asterisk extends Style {
+ override def skipFirstLine: Boolean = true
+ }
+ case object SpaceAsterisk extends Style {
+ override def skipFirstLine: Boolean = false
+ }
+ case object AsteriskSpace extends Style {
+ override def skipFirstLine: Boolean = false
+ }
+
+ implicit val reader: ConfCodec[Style] =
+ ReaderUtil.oneOf[Style](Asterisk, SpaceAsterisk, AsteriskSpace)
+
+ sealed abstract class Oneline
+ object Oneline {
+ case object keep extends Oneline
+ case object fold extends Oneline
+ case object unfold extends Oneline
+ implicit val reader: ConfCodec[Oneline] =
+ ReaderUtil.oneOf[Oneline](keep, fold, unfold)
+ }
+
+ sealed abstract class Wrap
+ object Wrap {
+ case object no extends Wrap
+ case object yes extends Wrap
+ implicit val codec: ConfCodec[Wrap] =
+ ReaderUtil.oneOf[Wrap](no, yes)
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ImportSelectors.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ImportSelectors.scala
index c7b54247e6..f79f69b370 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ImportSelectors.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ImportSelectors.scala
@@ -38,7 +38,6 @@ import metaconfig._
* // max columns |
* import org.{Aaaa, Bbbb, C, D, Eeee}
* }}}
- *
*/
sealed abstract class ImportSelectors extends Decodable[ImportSelectors] {
override protected[config] def baseDecoder = ImportSelectors.reader
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/IndentOperator.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/IndentOperator.scala
index 9add116881..789e23db80 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/IndentOperator.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/IndentOperator.scala
@@ -3,7 +3,6 @@ package org.scalafmt.config
import metaconfig._
/**
- *
* @param include
* Regexp for which infix operators should
* indent by 2 spaces. For example, .*=
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Newlines.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Newlines.scala
index 670ad44cd1..8edad59d12 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Newlines.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Newlines.scala
@@ -1,6 +1,6 @@
package org.scalafmt.config
-import org.scalafmt.config.Newlines.AfterInfix
+import org.scalafmt.config.Newlines._
import metaconfig._
import metaconfig.generic.Surface
@@ -134,31 +134,36 @@ import metaconfig.generic.Surface
* @param topLevelStatementsMinBreaks
* Minimum span (number of line breaks between first and last line)
* to start forcing blank lines.
+ * @param avoidForSimpleOverflow
+ * - punct: don't force break if overflow is only due to trailing punctuation
+ * - tooLong: don't force break if overflow is due to tokens which are too long
+ * and would likely overflow even after a break
*/
case class Newlines(
- source: Newlines.SourceHints = Newlines.classic,
+ source: SourceHints = Newlines.classic,
neverInResultType: Boolean = false,
neverBeforeJsNative: Boolean = false,
sometimesBeforeColonInMethodReturnType: Boolean = true,
penalizeSingleSelectMultiArgList: Boolean = true,
alwaysBeforeCurlyBraceLambdaParams: Boolean = false,
topLevelStatementsMinBreaks: Int = 1,
- topLevelStatements: Seq[Newlines.BeforeAfter] = Seq.empty,
+ topLevelStatements: Seq[BeforeAfter] = Seq.empty,
@annotation.DeprecatedName(
"alwaysBeforeTopLevelStatements",
"Use newlines.topLevelStatements instead",
"2.5.0"
)
alwaysBeforeTopLevelStatements: Boolean = false,
- afterCurlyLambda: NewlineCurlyLambda = NewlineCurlyLambda.never,
- implicitParamListModifierForce: Seq[Newlines.BeforeAfter] = Seq.empty,
- implicitParamListModifierPrefer: Option[Newlines.BeforeAfter] = None,
+ afterCurlyLambda: AfterCurlyLambdaParams = AfterCurlyLambdaParams.never,
+ implicitParamListModifierForce: Seq[BeforeAfter] = Seq.empty,
+ implicitParamListModifierPrefer: Option[BeforeAfter] = None,
alwaysBeforeElseAfterCurlyIf: Boolean = false,
alwaysBeforeMultilineDef: Boolean = true,
afterInfix: Option[AfterInfix] = None,
afterInfixBreakOnNested: Boolean = false,
afterInfixMaxCountPerExprForSome: Int = 10,
afterInfixMaxCountPerFile: Int = 500,
+ avoidForSimpleOverflow: Seq[AvoidForSimpleOverflow] = Seq.empty,
avoidAfterYield: Boolean = true
) {
if (
@@ -172,14 +177,14 @@ case class Newlines(
}
val reader: ConfDecoder[Newlines] = generic.deriveDecoder(this).noTypos
- if (source != Newlines.classic) Newlines.warnSourceIsExperimental
+ if (source != Newlines.classic) warnSourceIsExperimental
@inline
- def sourceIs(hint: Newlines.SourceHints): Boolean =
+ def sourceIs(hint: SourceHints): Boolean =
hint eq source
@inline
- def sourceIn(hints: Newlines.SourceHints*): Boolean =
+ def sourceIn(hints: SourceHints*): Boolean =
hints.contains(source)
val sourceIgnored: Boolean =
@@ -205,12 +210,12 @@ case class Newlines(
}
lazy val forceBeforeImplicitParamListModifier: Boolean =
- implicitParamListModifierForce.contains(Newlines.before)
+ implicitParamListModifierForce.contains(before)
lazy val forceAfterImplicitParamListModifier: Boolean =
- implicitParamListModifierForce.contains(Newlines.after)
+ implicitParamListModifierForce.contains(after)
private def preferBeforeImplicitParamListModifier: Boolean =
- implicitParamListModifierPrefer.contains(Newlines.before)
+ implicitParamListModifierPrefer.contains(before)
lazy val notPreferAfterImplicitParamListModifier: Boolean =
implicitParamListModifierForce.nonEmpty ||
preferBeforeImplicitParamListModifier
@@ -221,10 +226,15 @@ case class Newlines(
!forceBeforeImplicitParamListModifier
lazy val forceBlankBeforeMultilineTopLevelStmt: Boolean =
- topLevelStatements.contains(Newlines.before) ||
+ topLevelStatements.contains(before) ||
alwaysBeforeTopLevelStatements
lazy val forceBlankAfterMultilineTopLevelStmt: Boolean =
- topLevelStatements.contains(Newlines.after)
+ topLevelStatements.contains(after)
+
+ lazy val avoidForSimpleOverflowPunct: Boolean =
+ avoidForSimpleOverflow.contains(AvoidForSimpleOverflow.punct)
+ lazy val avoidForSimpleOverflowTooLong: Boolean =
+ avoidForSimpleOverflow.contains(AvoidForSimpleOverflow.tooLong)
}
object Newlines {
@@ -266,17 +276,22 @@ object Newlines {
implicit val beforeAfterReader: ConfCodec[BeforeAfter] =
ReaderUtil.oneOf[BeforeAfter](before, after)
-}
-
-sealed abstract class NewlineCurlyLambda
-
-object NewlineCurlyLambda {
+ sealed abstract class AvoidForSimpleOverflow
+ object AvoidForSimpleOverflow {
+ case object punct extends AvoidForSimpleOverflow
+ case object tooLong extends AvoidForSimpleOverflow
+ implicit val codec: ConfCodec[AvoidForSimpleOverflow] =
+ ReaderUtil.oneOf[AvoidForSimpleOverflow](punct, tooLong)
+ }
- case object preserve extends NewlineCurlyLambda
- case object always extends NewlineCurlyLambda
- case object never extends NewlineCurlyLambda
- case object squash extends NewlineCurlyLambda
+ sealed abstract class AfterCurlyLambdaParams
+ object AfterCurlyLambdaParams {
+ case object preserve extends AfterCurlyLambdaParams
+ case object always extends AfterCurlyLambdaParams
+ case object never extends AfterCurlyLambdaParams
+ case object squash extends AfterCurlyLambdaParams
+ implicit val codec: ConfCodec[AfterCurlyLambdaParams] =
+ ReaderUtil.oneOf[AfterCurlyLambdaParams](preserve, always, never, squash)
+ }
- implicit val newlineCurlyLambdaReader: ConfCodec[NewlineCurlyLambda] =
- ReaderUtil.oneOf[NewlineCurlyLambda](preserve, always, never, squash)
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/OptIn.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/OptIn.scala
index 0d9709b58d..3002b19947 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/OptIn.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/OptIn.scala
@@ -4,7 +4,6 @@ import metaconfig._
import metaconfig.generic.Surface
/**
- *
* @param configStyleArguments Call-sites where there is a newline after
* opening ( and newline before closing ).
* If true, preserves the newlines and keeps one
@@ -46,6 +45,25 @@ import metaconfig.generic.Surface
* foo.map(_ + 1).filter( > 2)
* }}}
*
+ * @param encloseClassicChains
+ * NB: ignored unless newlines.source=classic.
+ * Controls what happens if a chain enclosed in parentheses is followed by
+ * additional selects. Those additional selects will be considered part of
+ * the enclosed chain if and only if this flag is false.
+ * {{{
+ * // original
+ * (foo.map(_ + 1).map(_ + 1))
+ * .filter(_ > 2)
+ * // if true
+ * (foo.map(_ + 1).map(_ + 1))
+ * .filter(_ > 2)
+ * // if false
+ * (foo
+ * .map(_ + 1)
+ * .map(_ + 1))
+ * .filter(_ > 2)
+ * }}}
+ *
* @param annotationNewlines
* - if newlines.source is missing or keep:
* - if true, will keep existing line breaks around annotations
@@ -86,6 +104,7 @@ case class OptIn(
configStyleArguments: Boolean = true,
breaksInsideChains: Boolean = false,
breakChainOnFirstMethodDot: Boolean = true,
+ encloseClassicChains: Boolean = false,
selfAnnotationNewline: Boolean = true,
annotationNewlines: Boolean = true,
// Candidate to become default false at some point.
@@ -109,7 +128,7 @@ case class OptIn(
* configured in .scalafmt.conf
* if `forceBlankLineBeforeDocstring` configured to non-default value
* don't look at the old name
- * */
+ */
lazy val forceNewlineBeforeDocstringSummary: Boolean =
forceBlankLineBeforeDocstring && !blankLineBeforeDocstring
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfDecoders.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfDecoders.scala
index 069873bb31..7d3347945f 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfDecoders.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfDecoders.scala
@@ -1,8 +1,6 @@
package org.scalafmt.config
import scala.io.Codec
-import scala.meta.Dialect
-import scala.meta.dialects._
import scala.meta.parsers.Parse._
import scala.util.control.NonFatal
@@ -24,17 +22,6 @@ trait ScalafmtConfDecoders {
ReaderUtil.oneOf[MetaParser](parseSource, parseStat, parseCase)
}
- implicit lazy val dialectReader: ConfDecoder[Dialect] =
- ReaderUtil.oneOf[Dialect](
- Scala211,
- Scala212,
- Sbt0137,
- Sbt1,
- Dotty,
- Paradise211,
- Paradise212
- )
-
implicit lazy val codecReader: ConfDecoder[Codec] =
ConfDecoder.instance[Codec] {
case Conf.Str(s) =>
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala
index 6167bfaa1c..32254a6d58 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala
@@ -19,10 +19,6 @@ import org.scalafmt.util.ValidationOps
* the plan is to use this field for the IntelliJ+sbt integrations.
* @param maxColumn Column limit, any formatting exceeding this field is
* penalized heavily.
- * @param docstrings Several options:
- * - ScalaDoc: format as Scala docs
- * - JavaDocs: format as Java docs
- * - preserve: keep existing formatting
* @param assumeStandardLibraryStripMargin If true, the margin character | is treated
* as the new indentation in multiline strings
* ending with `.stripMargin`.
@@ -125,7 +121,8 @@ import org.scalafmt.util.ValidationOps
case class ScalafmtConfig(
version: String = org.scalafmt.Versions.stable,
maxColumn: Int = 80,
- docstrings: Docstrings = Docstrings.ScalaDoc,
+ docstrings: Docstrings = Docstrings(),
+ comments: Comments = Comments(),
optIn: OptIn = OptIn(),
binPack: BinPack = BinPack(),
continuationIndent: ContinuationIndent = ContinuationIndent(),
@@ -160,48 +157,16 @@ case class ScalafmtConfig(
encoding: Codec = "UTF-8",
project: ProjectFiles = ProjectFiles(),
fileOverride: Conf.Obj = Conf.Obj.empty,
+ xmlLiterals: XmlLiterals = XmlLiterals(),
edition: Edition = Edition.Latest
) {
- val allErrors = new mutable.ArrayBuffer[String]
- locally {
- import ValidationOps._
- implicit val errors = new mutable.ArrayBuffer[String]
- if (newlines.sourceIgnored) {
- addIf(newlines.afterCurlyLambda == NewlineCurlyLambda.preserve)
- addIf(optIn.configStyleArguments && align.openParenCallSite)
- addIf(optIn.configStyleArguments && align.openParenDefnSite)
- }
- if (newlines.source == Newlines.unfold) {
- addIf(align.arrowEnumeratorGenerator)
- }
- if (newlines.source != Newlines.classic) {
- addIf(optIn.breaksInsideChains)
- addIf(!includeCurlyBraceInSelectChains)
- }
- if (errors.nonEmpty) {
- val prefix = s"newlines.source=${newlines.source} and ["
- allErrors += errors.mkString(prefix, ",", "]")
- }
- }
- locally {
- implicit val errors = allErrors
- ValidationOps.addIf(align.ifWhileOpenParen && danglingParentheses.ctrlSite)
- if (!runner.dialect.allowTrailingCommas) {
- def err = " (no support in Scala dialect)"
- ValidationOps.addIf(trailingCommas == TrailingCommas.always, err)
- ValidationOps.addIf(trailingCommas == TrailingCommas.multiple, err)
- }
- }
- if (allErrors.nonEmpty) {
- val msg = allErrors.mkString("can't use: [\n\t", "\n\t", "\n]")
- throw new ScalafmtConfigException(msg)
- }
private implicit def runnerReader = runner.reader
private implicit def projectReader = project.reader
private implicit def rewriteReader = rewrite.reader
private implicit def spacesReader = spaces.reader
private implicit def literalsReader = literals.reader
+ private implicit def xmlLiteralsDecoder = xmlLiterals.decoder
private implicit def continuationIndentReader = continuationIndent.reader
private implicit def binpackReader = binPack.decoder
private implicit def newlinesReader = newlines.reader
@@ -211,6 +176,8 @@ case class ScalafmtConfig(
private implicit def danglingParenthesesReader = danglingParentheses.decoder
private implicit def indentOperatorReader = indentOperator.decoder
private implicit def importSelectorsReader = importSelectors.decoder
+ private implicit def docstringsDecoder = docstrings.decoder
+ private implicit def commentsDecoder = comments.decoder
lazy val alignMap: Map[String, regex.Pattern] =
align.tokens.map(x => x.code -> x.owner.r.pattern).toMap
private implicit val confObjReader = ScalafmtConfig.confObjReader
@@ -218,8 +185,8 @@ case class ScalafmtConfig(
implicit final lazy val decoder: ConfDecoder[ScalafmtConfig] =
new ConfDecoder[ScalafmtConfig] {
- override def read(conf: Conf): Configured[ScalafmtConfig] =
- (conf match {
+ override def read(conf: Conf): Configured[ScalafmtConfig] = {
+ val stylePreset = conf match {
case x: Conf.Obj =>
val section = Seq(Decodable.presetKey, "style").flatMap { y =>
x.field(y).map(y -> _)
@@ -228,13 +195,16 @@ case class ScalafmtConfig(
case (field, obj) => obj -> Conf.Obj((x.map - field).toList)
}
case _ => None
- }) match {
+ }
+ val parsed = stylePreset match {
case Some((styleConf, restConf)) =>
ScalafmtConfig
.readActiveStylePresets(styleConf)
.andThen(_.baseDecoder.read(restConf))
case _ => baseDecoder.read(conf)
}
+ parsed.andThen(ScalafmtConfig.validate)
+ }
}
def withDialect(dialect: Dialect): ScalafmtConfig =
@@ -242,13 +212,6 @@ case class ScalafmtConfig(
def forSbt: ScalafmtConfig = copy(runner = runner.forSbt)
- def reformatDocstrings: Boolean = docstrings != Docstrings.preserve
- def scalaDocs: Boolean = docstrings == Docstrings.ScalaDoc
- ValidationOps.assertNonNegative(
- continuationIndent.callSite,
- continuationIndent.defnSite
- )
-
private lazy val expandedFileOverride = Try {
val fs = file.FileSystems.getDefault
fileOverride.values.map {
@@ -279,6 +242,10 @@ case class ScalafmtConfig(
// Edition 2020-03
val activeForEdition_2020_03: Boolean = activeFor(Edition(2020, 3))
+
+ lazy val encloseSelectChains =
+ optIn.encloseClassicChains || !newlines.sourceIs(Newlines.classic)
+
}
object ScalafmtConfig {
@@ -323,7 +290,7 @@ object ScalafmtConfig {
binPack = BinPack(
unsafeDefnSite = true,
unsafeCallSite = true,
- parentConstructors = true
+ parentConstructors = BinPack.ParentCtors.Always
),
continuationIndent = ContinuationIndent(4, 4),
importSelectors = ImportSelectors.binPack,
@@ -336,7 +303,7 @@ object ScalafmtConfig {
// config style. It's fixable, but I don't want to spend time on it
// right now.
runner = conservativeRunner,
- docstrings = Docstrings.JavaDoc,
+ docstrings = default.docstrings.copy(style = Some(Docstrings.Asterisk)),
align = default.align.copy(
arrowEnumeratorGenerator = false,
tokens = Seq(AlignToken.caseArrow),
@@ -384,4 +351,52 @@ object ScalafmtConfig {
ConfError.message(err).notOk
}
+ private def validate(cfg: ScalafmtConfig): Configured[ScalafmtConfig] = {
+ import cfg._
+ import Newlines._
+ import ValidationOps._
+ val allErrors = new mutable.ArrayBuffer[String]
+ locally {
+ implicit val errors = new mutable.ArrayBuffer[String]
+ if (newlines.sourceIgnored) {
+ addIf(newlines.afterCurlyLambda == AfterCurlyLambdaParams.preserve)
+ addIf(optIn.configStyleArguments && align.openParenCallSite)
+ addIf(optIn.configStyleArguments && align.openParenDefnSite)
+ }
+ if (newlines.source == Newlines.unfold) {
+ addIf(align.arrowEnumeratorGenerator)
+ }
+ if (newlines.source != Newlines.classic) {
+ addIf(optIn.breaksInsideChains)
+ addIf(!includeCurlyBraceInSelectChains)
+ }
+ if (errors.nonEmpty) {
+ val prefix = s"newlines.source=${newlines.source} and ["
+ allErrors += errors.mkString(prefix, ",", "]")
+ }
+ }
+ locally {
+ implicit val errors = allErrors
+ addIf(align.ifWhileOpenParen && danglingParentheses.ctrlSite)
+ if (!runner.dialect.allowTrailingCommas) {
+ def err = " (no support in Scala dialect)"
+ addIf(trailingCommas == TrailingCommas.always, err)
+ addIf(trailingCommas == TrailingCommas.multiple, err)
+ }
+ addIfDirect( // can't use addIf on multiline conditions
+ (binPack.unsafeCallSite || binPack.unsafeDefnSite) && {
+ newlines.implicitParamListModifierForce.nonEmpty ||
+ newlines.implicitParamListModifierPrefer.nonEmpty
+ },
+ "binPack.unsafeXXX && newlines.implicitParamListModifierXXX (not implemented)"
+ )
+ addIfNegative(continuationIndent.callSite, continuationIndent.defnSite)
+ }
+ if (allErrors.isEmpty) Configured.ok(cfg)
+ else {
+ val msg = allErrors.mkString("can't use: [\n\t", "\n\t", "\n]")
+ Configured.notOk(ConfError.message(msg))
+ }
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtOptimizer.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtOptimizer.scala
index 5f1109520b..0846af7e26 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtOptimizer.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtOptimizer.scala
@@ -63,7 +63,7 @@ import metaconfig._
case class ScalafmtOptimizer(
dequeueOnNewStatements: Boolean = true,
escapeInPathologicalCases: Boolean = true,
- maxVisitsPerToken: Int = 513,
+ maxVisitsPerToken: Int = 10000,
maxEscapes: Int = 16,
maxDepth: Int = 100,
acceptOptimalAtHints: Boolean = true,
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtRunner.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtRunner.scala
index 9aac1e3ca8..7077160117 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtRunner.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtRunner.scala
@@ -3,7 +3,6 @@ package org.scalafmt.config
import metaconfig._
import scala.meta.Dialect
import scala.meta.Tree
-import scala.meta.dialects.Scala213
import scala.meta.parsers.Parse
import scala.meta.parsers.Parsed
@@ -21,29 +20,24 @@ case class ScalafmtRunner(
parser: Parse[_ <: Tree] = Parse.parseSource,
optimizer: ScalafmtOptimizer = ScalafmtOptimizer.default,
maxStateVisits: Int = 1000000,
- dialect: Dialect = ScalafmtRunner.defaultDialect,
+ dialect: Dialect = ScalafmtRunner.Dialect.default,
ignoreWarnings: Boolean = false,
fatalWarnings: Boolean = false
) {
implicit val optimizeDecoder = optimizer.reader
+ implicit def dialectDecoder = ScalafmtRunner.Dialect.decoder
val reader: ConfDecoder[ScalafmtRunner] = generic.deriveDecoder(this).noTypos
def forSbt: ScalafmtRunner =
copy(
- dialect = dialect.copy(
- allowToplevelTerms = true,
- toplevelSeparator = ""
- )
+ dialect = dialect
+ .withAllowToplevelTerms(true)
+ .withToplevelSeparator("")
)
private lazy val correctedDialect: Dialect = {
// without allowTraitParameters, our code handling "extends" wouldn't work
// owner of "extends" would be Name.Anonymous, expects Trait or Template
- dialect
- .copy(allowTraitParameters = true)
- // this must be explicit, .copy() loses it
- .withAllowNumericLiteralUnderscoreSeparators(
- dialect.allowNumericLiteralUnderscoreSeparators
- )
+ dialect.withAllowTraitParameters(true)
}
def event(evt: => FormatEvent): Unit =
@@ -68,38 +62,6 @@ object ScalafmtRunner {
ConfEncoder.StringEncoder.contramap(_ => "")
implicit lazy val dialectEncoder: ConfEncoder[Dialect] =
ConfEncoder.StringEncoder.contramap(_ => "")
- val defaultDialect = Scala213
- .copy(
- // Are `&` intersection types supported by this dialect?
- allowAndTypes = true,
- // Are extractor varargs specified using ats, i.e. is `case Extractor(xs @ _*)` legal or not?
- allowAtForExtractorVarargs = true,
- // Are extractor varargs specified using colons, i.e. is `case Extractor(xs: _*)` legal or not?
- allowColonForExtractorVarargs = true,
- // Are `inline` identifiers supported by this dialect?
- allowInlineIdents = true,
- // Are inline vals and defs supported by this dialect?
- allowInlineMods = false,
- // Are literal types allowed, i.e. is `val a : 42 = 42` legal or not?
- allowLiteralTypes = true,
- // Are `|` (union types) supported by this dialect?
- allowOrTypes = true,
- // Are trailing commas allowed? SIP-27.
- allowTrailingCommas = true,
- // Are trait allowed to have parameters?
- // They are in Dotty, but not in Scala 2.12 or older.
- allowTraitParameters = true,
- // Are view bounds supported by this dialect?
- // Removed in Dotty.
- allowViewBounds = true,
- // Are `with` intersection types supported by this dialect?
- allowWithTypes = true,
- // Are XML literals supported by this dialect?
- // We plan to deprecate XML literal syntax, and some dialects
- // might go ahead and drop support completely.
- allowXmlLiterals = true
- )
- .withAllowNumericLiteralUnderscoreSeparators(true)
/**
* The default runner formats a compilation unit and listens to no events.
@@ -120,4 +82,33 @@ object ScalafmtRunner {
val sbt = default.forSbt
+ object Dialect {
+ import scala.meta.dialects._
+
+ val scala212 = Scala212
+ .withAllowTrailingCommas(true) // SIP-27, 2.12.2
+ val scala213 = Scala213
+ .withAllowTrailingCommas(true)
+ val default = scala213
+ .withAllowOrTypes(true) // New feature in Dotty
+ .withAllowAndTypes(true) // New feature in Dotty
+ .withAllowTraitParameters(true) // New feature in Dotty
+ .withAllowColonForExtractorVarargs(true) // New feature in Dotty
+
+ implicit lazy val decoder: ConfDecoder[Dialect] = {
+ ReaderUtil.oneOf[Dialect](
+ default,
+ Scala211,
+ scala212,
+ scala213,
+ Sbt0137,
+ Sbt1,
+ Dotty,
+ Paradise211,
+ Paradise212
+ )
+ }
+
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Spaces.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Spaces.scala
index 33d11eff78..8289e8719c 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Spaces.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Spaces.scala
@@ -4,7 +4,6 @@ import metaconfig._
import org.scalafmt.config.SpaceBeforeContextBound.Never
/**
- *
* @param beforeContextBoundColon formats [A: T] as [A : T]
* @param afterTripleEquals If true, formats ===( as === (
* @param inImportCurlyBraces
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/TrailingCommas.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/TrailingCommas.scala
index 1dd785033a..a6e505643f 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/TrailingCommas.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/TrailingCommas.scala
@@ -17,7 +17,6 @@ import metaconfig._
*
* When [[org.scalafmt.config.TrailingCommas.preserve]] is selected, existing
* trailing commas will be preserved, and no new ones will be added.
- *
*/
sealed abstract class TrailingCommas
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/XmlLiterals.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/XmlLiterals.scala
new file mode 100644
index 0000000000..b2e3784c08
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/XmlLiterals.scala
@@ -0,0 +1,15 @@
+package org.scalafmt.config
+
+import metaconfig._
+
+case class XmlLiterals(
+ assumeFormatted: Boolean = false
+) {
+ implicit val decoder: ConfDecoder[XmlLiterals] =
+ generic.deriveDecoder(this).noTypos
+}
+
+object XmlLiterals {
+ implicit val surface: generic.Surface[XmlLiterals] = generic.deriveSurface
+ implicit lazy val encoder: ConfEncoder[XmlLiterals] = generic.deriveEncoder
+}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala
index 2fa622de7d..a49e1ff42d 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/BestFirstSearch.scala
@@ -19,10 +19,9 @@ import org.scalafmt.util.TreeOps
* Implements best first search to find optimal formatting.
*/
private class BestFirstSearch private (
- val formatOps: FormatOps,
range: Set[Range],
formatWriter: FormatWriter
-) {
+)(implicit val formatOps: FormatOps) {
import Token._
import LoggerOps._
@@ -86,7 +85,9 @@ private class BestFirstSearch private (
def provided(formatToken: FormatToken): Split = {
// TODO(olafur) the indentation is not correctly set.
- val split = Split(Provided(formatToken.between.map(_.syntax).mkString), 0)
+ val split = new Split(Provided(formatToken), 0) {
+ override def activateFor(splitTag: SplitTag): Split = this
+ }
val result =
if (formatToken.left.is[LeftBrace])
split.withIndent(Num(2), matching(formatToken.left), ExpiresOn.Before)
@@ -160,13 +161,13 @@ private class BestFirstSearch private (
tokens(deepestYet.depth)
)
- val style = styleMap.at(splitToken)
+ implicit val style = styleMap.at(splitToken)
- if (curr.split != null && curr.split.modification.isNewline) {
+ if (curr.split != null && curr.split.isNL) {
val tokenHash = hash(splitToken.left)
if (
emptyQueueSpots.contains(tokenHash) ||
- dequeueOnNewStatements &&
+ dequeueOnNewStatements && curr.allAltAreNL &&
dequeueSpots.contains(tokenHash) &&
(depth > 0 || !isInsideNoOptZone(splitToken))
)
@@ -190,12 +191,13 @@ private class BestFirstSearch private (
)
} else {
val actualSplit = getActiveSplits(curr, maxCost)
+ val allAltAreNL = actualSplit.forall(_.isNL)
var optimalNotFound = true
actualSplit.foreach { split =>
- val nextState = curr.next(style, split, splitToken)
+ val nextState = curr.next(split, allAltAreNL)
val updateBest = !keepSlowStates && depth == 0 &&
- split.modification.isNewline && !best.contains(curr.depth)
+ split.isNL && !best.contains(curr.depth)
if (updateBest) {
best.update(curr.depth, nextState)
}
@@ -285,12 +287,11 @@ private class BestFirstSearch private (
if (activeSplits.isEmpty) null else state // dead end if empty
else {
val split = activeSplits.head
- if (split.modification.isNewline) state
+ if (split.isNL) state
else {
runner.event(Enqueue(split))
- val ft = tokens(state.depth)
- val style = styleMap.at(ft)
- val nextState = state.next(style, split, ft)
+ implicit val style = styleMap.at(tokens(state.depth))
+ val nextState = state.next(split, false)
traverseSameLine(nextState, depth)
}
}
@@ -344,6 +345,6 @@ object BestFirstSearch {
range: Set[Range],
formatWriter: FormatWriter
): SearchResult =
- new BestFirstSearch(formatOps, range, formatWriter).getBestPath
+ new BestFirstSearch(range, formatWriter)(formatOps).getBestPath
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala
index 187a88885d..0325bac0b4 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Decision.scala
@@ -9,7 +9,7 @@ case class Decision(formatToken: FormatToken, splits: Seq[Split]) {
import org.scalafmt.util.TokenOps._
def noNewlines: Seq[Split] =
- splits.filterNot(_.modification.isNewline)
+ splits.filterNot(_.isNL)
def onlyNewlinesWithFallback(default: => Split): Seq[Split] = {
val filtered = onlyNewlineSplits
@@ -26,7 +26,7 @@ case class Decision(formatToken: FormatToken, splits: Seq[Split]) {
onlyNewlineSplits
private def onlyNewlineSplits: Seq[Split] =
- splits.filter(_.modification.isNewline)
+ splits.filter(_.isNL)
def withSplits(splits: Seq[Split]): Decision = copy(splits = splits)
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala
index 990e0be7d6..00bf77db79 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatOps.scala
@@ -2,9 +2,9 @@ package org.scalafmt.internal
import java.{util => ju}
-import scala.collection.JavaConverters._
+import org.scalafmt.CompatCollections.JavaConverters._
import org.scalafmt.Error.UnexpectedTree
-import org.scalafmt.config.{NewlineCurlyLambda, Newlines, ScalafmtConfig}
+import org.scalafmt.config.{BinPack, Comments, Newlines, ScalafmtConfig}
import org.scalafmt.internal.Length.Num
import org.scalafmt.internal.Policy.NoPolicy
import org.scalafmt.util._
@@ -27,14 +27,17 @@ import scala.meta.{
Tree,
Type
}
-import scala.meta.prettyprinters.Structure
import scala.meta.tokens.Token
import scala.meta.tokens.{Token => T}
/**
* Helper functions for generating splits/policies for a given tree.
*/
-class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
+class FormatOps(
+ val tree: Tree,
+ baseStyle: ScalafmtConfig,
+ val filename: String = ""
+) {
val initStyle = {
val queue = new mutable.Queue[Tree]
queue += tree
@@ -49,6 +52,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
else baseStyle.copy(newlines = checkedNewlines)
}
val runner = initStyle.runner
+ import PolicyOps._
import TokenOps._
import TreeOps._
implicit val dialect = initStyle.runner.dialect
@@ -131,44 +135,20 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
ownersMap.get(hash(tok)).map(tree => tok -> tree)
}
- object `:chain:` {
- def unapply(tok: Token): Option[(Token, Vector[Term.Select])] = {
- val ft = tokens(tok)
- val openApply = tokens(ft, 1).right
- def startsOpenApply =
- isOpenApply(
- openApply,
- includeCurly = initStyle.includeCurlyBraceInSelectChains,
- includeNoParens = initStyle.includeNoParensInSelectChains
- )
- def isShortCurlyChain(chain: Vector[Term.Select]): Boolean =
- chain.length == 2 && {
- !(for {
- child <- chain.lastOption
- parent <- child.parent
- } yield isChainApplyParent(parent, child)).getOrElse(false)
- }
-
- ft.meta.leftOwner match {
- case t: Term.Select
- if startsOpenApply &&
- !existsParentOfType[Import](ft.meta.leftOwner) =>
- val chain = getSelectChain(t, Vector(t))
- if (openApply.is[T.LeftBrace] && isShortCurlyChain(chain)) None
- else Some(tok -> chain)
- case _ => None
- }
- }
- }
-
@inline def prev(tok: FormatToken): FormatToken = tokens(tok, -1)
@inline def next(tok: FormatToken): FormatToken = tokens(tok, 1)
- @tailrec
final def findFirst(start: FormatToken, end: Token)(
f: FormatToken => Boolean
): Option[FormatToken] = {
- if (start.left.start > end.start) None
+ findFirst(start, end.start)(f)
+ }
+
+ @tailrec
+ final def findFirst(start: FormatToken, end: Int)(
+ f: FormatToken => Boolean
+ ): Option[FormatToken] = {
+ if (start.left.start > end) None
else if (f(start)) Some(start)
else {
val next_ = next(start)
@@ -210,6 +190,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
None
case c: T.Comment
if style.activeForEdition_2020_01 && start.noBreak &&
+ (style.comments.wrap ne Comments.Wrap.trailing) &&
(!start.left.is[T.LeftParen] || isSingleLineComment(c)) =>
Some(c)
case _ => Some(start.left)
@@ -226,7 +207,9 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
_: T.Equals =>
None
case _: T.RightParen if start.left.is[T.LeftParen] => None
- case c: T.Comment if isSingleLineComment(c) && start.noBreak =>
+ case c: T.Comment
+ if isSingleLineComment(c) && start.noBreak &&
+ (style.comments.wrap ne Comments.Wrap.trailing) =>
Some(c)
case _ if start.noBreak && isInfix => None
case _ => Some(start.left)
@@ -260,14 +243,11 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
*
* Context: https://github.com/scalameta/scalafmt/issues/108
*/
- def isJsNative(jsToken: Token): Boolean = {
- initStyle.newlines.neverBeforeJsNative && jsToken.syntax == "js" &&
- owners(jsToken).parent.exists(
- _.show[
- Structure
- ].trim == """Term.Select(Term.Name("js"), Term.Name("native"))"""
- )
- }
+ def isJsNative(body: Tree): Boolean =
+ initStyle.newlines.neverBeforeJsNative && (body match {
+ case Term.Select(Term.Name("js"), Term.Name("native")) => true
+ case _ => false
+ })
@inline
final def startsStatement(tok: FormatToken): Option[Tree] =
@@ -362,58 +342,50 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
}.getOrElse(tree.tokens.last)
@inline
- def OneArgOneLineSplit(tok: FormatToken)(implicit
- line: sourcecode.Line,
- style: ScalafmtConfig
- ): Policy =
- if (style.poorMansTrailingCommasInConfigStyle)
- splitOneArgPerLineBeforeComma(tok)
- else
- splitOneArgPerLineAfterComma(tok)
-
- def splitOneArgPerLineBeforeComma(tok: FormatToken)(implicit
+ def splitOneArgOneLine(close: Token, owner: Tree)(implicit
line: sourcecode.Line,
style: ScalafmtConfig
): Policy = {
- val owner = tok.meta.leftOwner
- // TODO(olafur) clear queue between arguments, they are independent.
- Policy(matching(tok.left)) {
- case Decision(t @ FormatToken(_, _: T.Comma, _), splits)
- if owner == t.meta.rightOwner && !next(t).right.is[T.Comment] =>
- splits.map { x =>
- if (x.modification != NoSplit) x else x.copy(modification = Newline)
- }
-
- case Decision(t @ FormatToken(_: T.Comma, right, _), splits)
- if owner == t.meta.leftOwner &&
- !right.is[T.LeftBrace] &&
- // If comment is bound to comma, see unit/Comment.
- (!right.is[T.Comment] || t.hasBreak) =>
- val isNewline = right.is[T.Comment]
- splits.filter(_.modification.isNewline == isNewline)
- }
+ val pf =
+ if (style.poorMansTrailingCommasInConfigStyle)
+ splitOneArgPerLineBeforeComma(owner)
+ else
+ splitOneArgPerLineAfterComma(owner)
+ Policy.before(close)(pf)
}
- def splitOneArgPerLineAfterComma(tok: FormatToken)(implicit
- line: sourcecode.Line,
- style: ScalafmtConfig
- ): Policy = {
- val owner = tok.meta.leftOwner
+ def splitOneArgPerLineBeforeComma(
+ owner: Tree
+ )(implicit style: ScalafmtConfig): Policy.Pf = {
// TODO(olafur) clear queue between arguments, they are independent.
- Policy(matching(tok.left)) {
- // Newline on every comma.
- case Decision(t @ FormatToken(_: T.Comma, right, _), splits)
- if owner == t.meta.leftOwner &&
- // TODO(olafur) what the right { decides to be single line?
- // If comment is bound to comma, see unit/Comment.
- (!right.is[T.Comment] || t.hasBreak) =>
- if (!right.is[T.LeftBrace])
- splits.filter(_.modification.isNewline)
- else if (!style.activeForEdition_2020_03)
- splits
- else
- SplitTag.OneArgPerLine.activateOnly(splits)
- }
+ case Decision(t @ FormatToken(_, _: T.Comma, _), splits)
+ if owner == t.meta.rightOwner && !next(t).right.is[T.Comment] =>
+ splits.map(x => if (x.modExt.mod ne NoSplit) x else x.withMod(Newline))
+
+ case Decision(t @ FormatToken(_: T.Comma, right, _), splits)
+ if owner == t.meta.leftOwner &&
+ !right.is[T.LeftBrace] &&
+ // If comment is bound to comma, see unit/Comment.
+ (!right.is[T.Comment] || t.hasBreak) =>
+ val isNewline = right.is[T.Comment]
+ splits.filter(_.isNL == isNewline)
+ }
+
+ def splitOneArgPerLineAfterComma(
+ owner: Tree
+ )(implicit style: ScalafmtConfig): Policy.Pf = {
+ // Newline on every comma.
+ case Decision(t @ FormatToken(_: T.Comma, right, _), splits)
+ if owner == t.meta.leftOwner &&
+ // TODO(olafur) what the right { decides to be single line?
+ // If comment is bound to comma, see unit/Comment.
+ (!right.is[T.Comment] || t.hasBreak) =>
+ if (!right.is[T.LeftBrace])
+ splits.filter(_.isNL)
+ else if (!style.activeForEdition_2020_03)
+ splits
+ else
+ SplitTag.OneArgPerLine.activateOnly(splits)
}
def UnindentAtExclude(
@@ -425,32 +397,11 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
s.map(_.withIndent(indent, close, ExpiresOn.After))
}
- def penalizeAllNewlines(
- expire: Token,
- penalty: Int,
- penalizeLambdas: Boolean = true,
- ignore: FormatToken => Boolean = _ => false,
- penaliseNewlinesInsideTokens: Boolean = false
- )(implicit line: sourcecode.Line): Policy =
- Policy(expire) {
- case Decision(tok, s)
- if tok.right.end < expire.end &&
- (penalizeLambdas || !tok.left.is[T.RightArrow]) && !ignore(tok) =>
- s.map {
- case split
- if split.modification.isNewline ||
- (penaliseNewlinesInsideTokens && tok.leftHasNewline) =>
- split.withPenalty(penalty)
- case x => x
- }
- }
-
def penalizeNewlineByNesting(from: Token, to: Token)(implicit
line: sourcecode.Line
): Policy = {
- val range = Range(from.start, to.end).inclusive
- Policy(to) {
- case Decision(t, s) if range.contains(t.right.start) =>
+ Policy.before(to) {
+ case Decision(t, s) if t.right.start >= from.start =>
val nonBoolPenalty =
if (isBoolOperator(t.left)) 0
else 5
@@ -459,7 +410,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
nestedSelect(t.meta.leftOwner) + nestedApplies(t.meta.rightOwner) +
nonBoolPenalty
s.map {
- case split if split.modification.isNewline =>
+ case split if split.isNL =>
split.withPenalty(penalty)
case x => x
}
@@ -494,59 +445,23 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
}
}
- /**
- * Returns last token of select, handles case when select's parent is apply.
- *
- * For example, in:
- * foo.bar[T](1, 2)
- * the last token is the final )
- *
- * @param dot the dot owned by the select.
- */
- def getSelectsLastToken(dot: T.Dot): FormatToken = {
- var curr = tokens(dot, 1)
- while (
- isOpenApply(
- curr.right,
- includeCurly = true,
- includeNoParens = true
- ) &&
- !statementStarts.contains(hash(curr.right))
- ) {
- if (curr.right.is[T.Dot]) {
- curr = tokens(curr, 2)
- } else {
- curr = tokens(matching(curr.right))
- }
- }
- curr
- }
-
def getOptimalTokenFor(token: Token): Token =
getOptimalTokenFor(tokens(token))
def getOptimalTokenFor(ft: FormatToken): Token =
if (isAttachedSingleLineComment(ft)) ft.right else ft.left
- def getSelectOptimalToken(tree: Tree): Token = {
- val lastDotOpt = findLast(tree.tokens)(_.is[T.Dot])
- if (lastDotOpt.isEmpty)
- throw new IllegalStateException(s"Missing . in select $tree")
- val lastDot = lastDotOpt.get.asInstanceOf[T.Dot]
- lastToken(getSelectsLastToken(lastDot).meta.leftOwner)
- }
-
def infixIndent(
app: InfixApp,
formatToken: FormatToken,
isNewline: Boolean
)(implicit style: ScalafmtConfig): Int = {
if (style.verticalAlignMultilineOperators)
- if (InfixApp.isAssignment(formatToken.left.syntax)) 2 else 0
+ if (InfixApp.isAssignment(formatToken.meta.left.text)) 2 else 0
else if (
!app.rhs.headOption.exists { x =>
x.is[Term.Block] || x.is[Term.NewAnonymous]
- } && isInfixTopLevelMatch(app.all, formatToken.left.syntax, false)
+ } && isInfixTopLevelMatch(app.all, formatToken.meta.left.text, false)
) 2
else if (isInfixTopLevelMatch(app.all, app.op.value, true)) 0
else if (!isNewline && !isSingleLineComment(formatToken.right)) 0
@@ -570,7 +485,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
def beforeInfixSplit(
owner: Term.ApplyInfix,
formatToken: FormatToken
- )(implicit line: sourcecode.Line, style: ScalafmtConfig): Seq[Split] = {
+ )(implicit style: ScalafmtConfig): Seq[Split] = {
val InfixApp(app) = owner
infixSplitImpl(app, formatToken, true)
}
@@ -579,7 +494,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
app: InfixApp,
formatToken: FormatToken,
beforeLhs: Boolean
- )(implicit line: sourcecode.Line, style: ScalafmtConfig): Seq[Split] = {
+ )(implicit style: ScalafmtConfig): Seq[Split] = {
// NOTE. Silly workaround because we call infixSplit from assignment =, see #798
val treeOpt =
if (!beforeLhs && app.isRightAssoc)
@@ -595,20 +510,24 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
// TODO: if that ever changes, modify how rewrite rules handle infix
val modification = getModCheckIndent(formatToken)
val isNewline = modification.isNewline
- val indent = infixIndent(app, formatToken, isNewline)
- val split =
- Split(modification, 0).withIndent(Num(indent), expire, ExpiresOn.After)
-
- if (
- !style.activeForEdition_2020_01 || !beforeLhs ||
- isNewline || formatToken.right.is[T.Comment]
- )
- Seq(split)
- else {
- val altIndent = infixIndent(app, formatToken, true)
+ val asIs = !style.activeForEdition_2020_01 || !beforeLhs ||
+ (isNewline && !style.newlines.sourceIgnored) ||
+ formatToken.right.is[T.Comment]
+ if (asIs) {
+ val indent = infixIndent(app, formatToken, isNewline)
+ Seq(
+ Split(modification, 0).withIndent(Num(indent), expire, ExpiresOn.After)
+ )
+ } else {
+ val spcIndent = infixIndent(app, formatToken, false)
+ val nlIndent = infixIndent(app, formatToken, true)
+ val nlCost = if (style.newlines.formatInfix) 2 else 1
+ val (spcMod, nlMod) =
+ if (isNewline) (Space, modification)
+ else (modification, Newline)
Seq(
- split,
- Split(Newline, 1).withIndent(Num(altIndent), expire, ExpiresOn.After)
+ Split(spcMod, 0).withIndent(Num(spcIndent), expire, ExpiresOn.After),
+ Split(nlMod, nlCost).withIndent(Num(nlIndent), expire, ExpiresOn.After)
)
}
}
@@ -616,7 +535,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
def insideInfixSplit(
app: InfixApp,
ft: FormatToken
- )(implicit line: sourcecode.Line, style: ScalafmtConfig): Seq[Split] =
+ )(implicit style: ScalafmtConfig): Seq[Split] =
app.all match {
case t: Type.ApplyInfix
if style.spaces.neverAroundInfixTypes.contains(t.op.value) =>
@@ -643,7 +562,8 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
ft: FormatToken,
newStmtModOrBody: Either[Modification, Tree]
)(implicit style: ScalafmtConfig): Seq[Split] = {
- val fullInfixTreeOpt = findTreeWithParentSimple(lhsApp.all)(!isInfixApp(_))
+ val fullInfixTreeOpt =
+ findTreeWithParentSimple(lhsApp.all, false)(isInfixApp)
val fullInfix = fullInfixTreeOpt.flatMap(asInfixApp).getOrElse(lhsApp)
val app = findLeftInfix(fullInfix)
newStmtModOrBody.fold(
@@ -651,7 +571,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
x => {
val indent = Indent(Num(2), x.tokens.last, ExpiresOn.After)
getInfixSplitsBeforeLhsOrRhs(app, ft, fullInfix).map { s =>
- if (s.modification.isNewline) s.withIndent(indent) else s
+ if (s.isNL) s.withIndent(indent) else s
}
}
)
@@ -717,15 +637,12 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
val nlPolicy =
if (nlIndent eq Indent.Empty) NoPolicy
else
- Policy(fullExpire) {
+ Policy.on(fullExpire) {
case Decision(t: FormatToken, s) if isInfixOp(t.meta.leftOwner) =>
if (isSingleLineComment(t.right)) // will break
s.map(_.switch(firstInfixOp))
else
- s.map { x =>
- if (!x.modification.isNewline) x
- else x.switch(firstInfixOp)
- }
+ s.map(x => if (x.isNL) x.switch(firstInfixOp) else x)
}
val singleLineExpire = if (isFirst) fullExpire else expires.head._1
@@ -736,11 +653,11 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
.onlyIf(singleLinePolicy.isDefined && beforeLhs)
.withIndent(nlIndentLength, singleLineExpire, ExpiresOn.After)
.withSingleLine(singleLineExpire)
- .andThenPolicyOpt(singleLinePolicy)
+ .andPolicyOpt(singleLinePolicy)
val spaceSingleLine = Split(Space, 0)
.onlyIf(newStmtMod.isEmpty)
.withSingleLine(singleLineExpire)
- .andThenPolicyOpt(singleLinePolicy)
+ .andPolicyOpt(singleLinePolicy)
val singleLineSplits = Seq(
spaceSingleLine.onlyFor(SplitTag.InfixChainNoNL),
spaceSingleLine.onlyIf(singleLinePolicy.isDefined),
@@ -765,18 +682,18 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
val breakAfterClose = endOfNextOp.flatMap { tok =>
val end = nextNonCommentSameLine(tokens(tok))
if (end.right.is[T.LeftBrace]) None
- else Some(Policy.map(decideNewlinesOnlyAfterToken)(end.left))
+ else Some(decideNewlinesOnlyAfterToken(end.left))
}
val nlSplit = Split(nlMod, 0)
- .andThenPolicyOpt(breakAfterClose)
+ .andPolicyOpt(breakAfterClose)
.withIndent(nlIndent)
.withPolicy(nlPolicy)
val singleLineSplit = Split(Space, 0)
.notIf(noSingleLine)
.withSingleLine(endOfNextOp.getOrElse(close))
- .andThenPolicyOpt(breakAfterClose)
- .andThenPolicy(getSingleLineInfixPolicy(close))
+ .andPolicyOpt(breakAfterClose)
+ .andPolicy(getSingleLineInfixPolicy(close))
Seq(singleLineSplit, nlSplit)
}
@@ -790,7 +707,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
val exclude =
if (breakMany) Set.empty[Range]
else insideBlockRanges[LeftParenOrBrace](nextFT, expire)
- Split(newStmtMod.getOrElse(Space), cost)
+ Split(ModExt(newStmtMod.getOrElse(Space)), cost)
.withSingleLine(expire, exclude)
}
}
@@ -799,7 +716,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
}
def getSingleLineInfixPolicy(end: Token) =
- Policy(end) {
+ Policy.on(end) {
case Decision(t: FormatToken, s) if isInfixOp(t.meta.leftOwner) =>
SplitTag.InfixChainNoNL.activateOnly(s)
}
@@ -911,30 +828,42 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
def noOptimizationZones(tree: Tree): Set[Token] = {
val result = Set.newBuilder[Token]
- var inside = false
- var expire = tree.tokens.head
+ var expire: Token = null
tree.tokens.foreach {
- case t if !inside && ((t, ownersMap(hash(t))) match {
- case (T.LeftParen(), _: Term.Apply | _: Init) =>
- // TODO(olafur) https://github.com/scalameta/scalameta/issues/345
- val x = true
- x
- // Type compounds can be inside defn.defs
- case (T.LeftBrace(), Type.Refine(_, _)) => true
- case _ => false
- }) =>
- inside = true
- expire = matching(t)
- case x if x == expire => inside = false
- case x if inside => result += x
+ case x if expire ne null =>
+ if (x eq expire) expire = null else result += x
+ case t: T.LeftParen =>
+ owners(t) match {
+ // TODO(olafur) https://github.com/scalameta/scalameta/issues/345
+ case _: Term.Apply | _: Init => expire = matching(t)
+ case _ =>
+ }
+ case t: T.LeftBrace =>
+ owners(t) match {
+ // Type compounds can be inside defn.defs
+ case _: Type.Refine => expire = matching(t)
+ case _ =>
+ }
case _ =>
}
result.result()
}
+ def mustUseConfigStyle(
+ ft: FormatToken,
+ allowForce: => Boolean = true
+ )(implicit style: ScalafmtConfig): Boolean =
+ style.optIn.configStyleArguments && couldUseConfigStyle(ft, allowForce)
+
+ def couldUseConfigStyle(
+ ft: FormatToken,
+ allowForce: => Boolean = true
+ )(implicit style: ScalafmtConfig): Boolean =
+ opensConfigStyle(ft) || allowForce && forceConfigStyle(ft.meta.leftOwner)
+
def opensConfigStyle(
ft: => FormatToken,
- whenSourceIgnored: Boolean
+ whenSourceIgnored: Boolean = false
)(implicit style: ScalafmtConfig): Boolean =
if (style.newlines.sourceIgnored) whenSourceIgnored
else opensConfigStyleClassic(ft)
@@ -1006,73 +935,106 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
nonWhitespaceOffset(right) - nonWhitespaceOffset(left)
}
- def ctorWithChain(ownerSet: Set[Tree], lastToken: Token): Policy =
- if (styleMap.at(tokens(lastToken)).binPack.parentConstructors)
- NoPolicy
+ def ctorWithChain(
+ ownerSet: Set[Tree],
+ lastToken: Token
+ )(implicit style: ScalafmtConfig): Policy =
+ if (style.binPack.parentConstructors eq BinPack.ParentCtors.Always) NoPolicy
+ else if (ownerSet.isEmpty) NoPolicy
else
- Policy(lastToken) {
+ Policy.before(lastToken) {
case d @ Decision(t @ FormatToken(_, _: T.KwWith, _), _)
if ownerSet.contains(t.meta.rightOwner) =>
d.onlyNewlinesWithoutFallback
}
def binPackParentConstructorSplits(
- owners: Set[Tree],
+ chain: Either[Template, Seq[Type.With]],
lastToken: Token,
- indent: Int
- )(implicit line: sourcecode.Line): Seq[Split] = {
+ indentLen: Int
+ )(implicit line: sourcecode.Line, style: ScalafmtConfig): Seq[Split] = {
+ val nlMod = NewlineT(alt = Some(Space))
+ val owners = chain.fold[Set[Tree]](Set(_), x => x.toSet)
+ val nlPolicy = ctorWithChain(owners, lastToken)
+ val nlOnelineTag = style.binPack.parentConstructors match {
+ case BinPack.ParentCtors.Oneline => Right(true)
+ case BinPack.ParentCtors.OnelineIfPrimaryOneline =>
+ Left(SplitTag.OnelineWithChain)
+ case BinPack.ParentCtors.Always | BinPack.ParentCtors.Never =>
+ Right(false)
+ case BinPack.ParentCtors.MaybeNever =>
+ Right(style.newlines.sourceIs(Newlines.fold))
+ }
+ val indent = Indent(Num(indentLen), lastToken, ExpiresOn.After)
+ val extendsThenWith = chain.left.exists(_.inits.length > 1)
Seq(
- Split(Space, 0)
- .withPolicy(SingleLineBlock(lastToken))
- .withIndent(Num(indent), lastToken, ExpiresOn.After),
- Split(NewlineT(acceptSpace = true), 1)
- .withPolicy(ctorWithChain(owners, lastToken))
- .withIndent(Num(indent), lastToken, ExpiresOn.After)
+ Split(Space, 0).withSingleLine(lastToken, noSyntaxNL = extendsThenWith),
+ Split(nlMod, 0)
+ .onlyIf(nlOnelineTag != Right(false))
+ .preActivateFor(nlOnelineTag.left.toOption)
+ .withSingleLine(lastToken, noSyntaxNL = extendsThenWith)
+ .withIndent(indent),
+ Split(nlMod, 1).withPolicy(nlPolicy).withIndent(indent)
)
}
- def delayedBreakPolicy(
- leftCheck: Option[Token => Boolean]
- )(onBreakPolicy: Policy)(implicit line: sourcecode.Line): Policy = {
+ def delayedBreakPolicyFactory(onBreakPolicy: Policy): Policy.Pf = {
object OnBreakDecision {
- def unapply(d: Decision): Option[Seq[Split]] =
- if (leftCheck.exists(!_(d.formatToken.left))) None
- else unapplyImpl(d)
- private def unapplyImpl(d: Decision): Option[Seq[Split]] = {
+ def unapply(d: Decision): Option[Seq[Split]] = {
var replaced = false
def decisionPf(s: Split): Split =
- if (!s.modification.isNewline) s
+ if (!s.isNL) s
else {
replaced = true
- s.orElsePolicy(onBreakPolicy)
+ s.orPolicy(onBreakPolicy)
}
val splits = d.splits.map(decisionPf)
if (replaced) Some(splits) else None
}
}
- if (onBreakPolicy.isEmpty) onBreakPolicy
- else onBreakPolicy.copy(f = { case OnBreakDecision(d) => d })
+ {
+ case OnBreakDecision(d) => d
+ }
}
- def newlinesOnlyBeforeClosePolicy(close: Token)(implicit
- line: sourcecode.Line
- ): Policy =
- Policy.map(decideNewlinesOnlyBeforeClose(Split(Newline, 0)))(close)
+ def delayedBreakPolicy(
+ end: Policy.End.WithPos
+ )(onBreakPolicy: Policy)(implicit line: sourcecode.Line): Policy =
+ delayedBreakPolicy(Option(end))(onBreakPolicy)
- def decideNewlinesOnlyBeforeClose(split: Split)(close: Token): Policy.Pf = {
- case d: Decision if d.formatToken.right eq close =>
- d.onlyNewlinesWithFallback(split)
- }
+ def delayedBreakPolicy(
+ end: Option[Policy.End.WithPos] = None
+ )(onBreakPolicy: Policy)(implicit line: sourcecode.Line): Policy =
+ Policy.Proxy(onBreakPolicy, end)(delayedBreakPolicyFactory)
- def decideNewlinesOnlyAfterClose(split: Split)(close: Token): Policy.Pf = {
- case d: Decision if d.formatToken.left eq close =>
- d.onlyNewlinesWithFallback(split)
- }
+ def decideNewlinesOnlyBeforeClose(
+ close: Token
+ )(implicit line: sourcecode.Line): Policy =
+ decideNewlinesOnlyBeforeClose(Split(Newline, 0))(close)
+
+ def decideNewlinesOnlyBeforeClose(
+ split: Split
+ )(close: Token)(implicit line: sourcecode.Line): Policy =
+ Policy.on(close) {
+ case d: Decision if d.formatToken.right eq close =>
+ d.onlyNewlinesWithFallback(split)
+ }
- def decideNewlinesOnlyAfterToken(token: Token): Policy.Pf = {
- case d: Decision if d.formatToken.left eq token =>
- d.onlyNewlinesWithoutFallback
- }
+ def decideNewlinesOnlyAfterClose(
+ split: Split
+ )(close: Token)(implicit line: sourcecode.Line): Policy =
+ Policy.after(close) {
+ case d: Decision if d.formatToken.left eq close =>
+ d.onlyNewlinesWithFallback(split)
+ }
+
+ def decideNewlinesOnlyAfterToken(
+ token: Token
+ )(implicit line: sourcecode.Line): Policy =
+ Policy.after(token) {
+ case d: Decision if d.formatToken.left eq token =>
+ d.onlyNewlinesWithoutFallback
+ }
def getForceConfigStyle: (Set[Tree], Set[TokenHash]) = {
val maxDistance = runner.optimizer.forceConfigStyleOnOffset
@@ -1107,33 +1069,24 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
val isBracket = open.is[T.LeftBracket]
@tailrec
- def loop(token: Token): Option[FormatToken] = {
- tokens(matching(token)) match {
- case FormatToken(RightParenOrBracket(), l @ T.LeftParen(), _) =>
- loop(l)
- case f @ FormatToken(RightParenOrBracket(), right, _) =>
- lazy val isCtorModifier =
- f.meta.rightOwner.parent.exists(_.is[meta.Ctor])
- right match {
- // modifier for constructor if class definition has type parameters: [class A[T, K, C] private (a: Int)]
- case Modifier() if isCtorModifier =>
- // This case only applies to classes
- next(f).right match {
- case x @ (_: T.LeftParen | _: T.LeftBracket) =>
- loop(x)
- case _ =>
- Some(f)
- }
- case _ =>
- Some(f)
+ def loop(token: Token): FormatToken = {
+ val f = tokens(token)
+ f.right match {
+ case x: T.LeftParen => loop(matching(x))
+ // modifier for constructor if class definition has type parameters: [class A[T, K, C] private (a: Int)]
+ case Modifier() if f.meta.rightOwner.parent.exists(_.is[Ctor]) =>
+ // This case only applies to classes
+ next(f).right match {
+ case x @ LeftParenOrBracket() => loop(matching(x))
+ case _ => f
}
- case _ => None
+ case _ => f
}
}
// find the last param on the defn so that we can apply our `policy`
// till the end.
- val lastParenFt = loop(open).get
+ val lastParenFt = loop(close)
val lastParen = lastParenFt.left
val mixedParams = {
@@ -1150,15 +1103,9 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
// create two (2) OneArgOneLineSplit when dealing with classes. One
// deals with the type params and the other with the value params.
val oneLinePerArg = {
- val base = OneArgOneLineSplit(ft)
- if (mixedParams) {
- val afterTypes = tokens(matching(open))
- // Try to find the first paren. If found, then we are dealing with
- // a class with type AND value params. Otherwise it is a class with
- // just type params.
- findFirst(afterTypes, lastParen)(t => t.left.is[T.LeftParen])
- .fold(base)(t => base.orElse(OneArgOneLineSplit(t)))
- } else base
+ val base = splitOneArgOneLine(lastParen, ft.meta.leftOwner)
+ if (!mixedParams || (close eq lastParen)) base
+ else base | splitOneArgOneLine(lastParen, lastParenFt.meta.leftOwner)
}
// DESNOTE(2017-03-28, pjrt) Classes and defs aren't the same.
@@ -1173,12 +1120,10 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
rpOwner == owner || rpOwner == valueParamsOwner
}
- val paramGroupSplitter: Policy.Pf = {
+ val paramGroupSplitter = Policy.on(lastParen) {
// If this is a class, then don't dangle the last paren unless the line ends with a comment
- case Decision(t @ FormatToken(previous, rp @ RightParenOrBracket(), _), _)
- if shouldNotDangle && rp == lastParen && !isSingleLineComment(
- previous
- ) =>
+ case Decision(FormatToken(previous, `lastParen`, _), _)
+ if shouldNotDangle && !isSingleLineComment(previous) =>
Seq(Split(NoSplit, 0))
// Indent seperators `)(` and `](` by `indentSep`
case Decision(t @ FormatToken(_, rp @ RightParenOrBracket(), _), _)
@@ -1214,7 +1159,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
// Our policy is a combination of OneArgLineSplit and a custom splitter
// for parameter groups.
- val policy = oneLinePerArg.orElse(paramGroupSplitter, lastParen.end)
+ val policy = oneLinePerArg | paramGroupSplitter
val firstIndent =
if (r.is[T.RightParen]) // An empty param group
@@ -1270,17 +1215,22 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
newlines: Int
)(implicit style: ScalafmtConfig): (Boolean, NewlineT) =
style.newlines.afterCurlyLambda match {
- case NewlineCurlyLambda.squash => (true, Newline)
- case NewlineCurlyLambda.never =>
+ case Newlines.AfterCurlyLambdaParams.squash => (true, Newline)
+ case Newlines.AfterCurlyLambdaParams.never =>
val space = style.newlines.source match {
case Newlines.fold => true
case Newlines.unfold => false
case _ => newlines == 0
}
(space, Newline)
- case NewlineCurlyLambda.always => (false, Newline2x)
- case NewlineCurlyLambda.preserve =>
- (newlines == 0, if (newlines >= 2) Newline2x else Newline)
+ case Newlines.AfterCurlyLambdaParams.always => (false, Newline2x)
+ case Newlines.AfterCurlyLambdaParams.preserve =>
+ val space = style.newlines.source match {
+ case Newlines.fold => true
+ case Newlines.unfold => false
+ case _ => newlines == 0
+ }
+ (space, if (newlines >= 2) Newline2x else Newline)
}
def getNoSplit(
@@ -1330,6 +1280,10 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
else tokens(nextNonComment(maybeArrow), 1)
}
}
+ .orElse {
+ val headToken = tokens(term.tokens.head)
+ findFirst(headToken, term.tokens.last)(_.left.is[T.RightArrow])
+ }
// look for arrow before body, if any, else after cond/pat
def getCaseArrow(term: Case): FormatToken =
@@ -1356,7 +1310,7 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
(name, getArgs(paramss))
else {
// XXX: backwards-compatible hack
- val useTParams = t.is[Defn.Def] ||
+ val useTParams = style.activeForEdition_2020_03 || t.is[Defn.Def] ||
t.is[Type.Param] || t.is[Decl.Type] || t.is[Defn.Type]
(name, if (useTParams) tparams else paramss.flatten)
}
@@ -1398,28 +1352,94 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
getClosingIfEnclosedInMatching(tree).isDefined
@tailrec
- final def findPrevSelect(tree: Tree): Option[Term.Select] =
+ final def findPrevSelect(tree: Tree, enclosed: Boolean): Option[Term.Select] =
tree match {
case t: Term.Select => Some(t)
case t @ SplitCallIntoParts(fun, _) if t ne fun =>
- if (isEnclosedInMatching(t)) None else findPrevSelect(fun)
+ if (enclosed && isEnclosedInMatching(t)) None
+ else findPrevSelect(fun, enclosed)
case _ => None
}
- def findPrevSelect(tree: Term.Select): Option[Term.Select] =
- findPrevSelect(tree.qual)
+ def findPrevSelect(
+ tree: Term.Select,
+ enclosed: Boolean = true
+ ): Option[Term.Select] =
+ findPrevSelect(tree.qual, enclosed)
@tailrec
- final def findLastApplyAndNextSelect(
+ private def findLastApplyAndNextSelectEnclosed(
tree: Tree,
select: Option[Term.Select] = None
+ ): (Tree, Option[Term.Select]) =
+ if (isEnclosedInMatching(tree)) (tree, select)
+ else
+ tree.parent match {
+ case Some(p: Term.Select) =>
+ findLastApplyAndNextSelectEnclosed(p, select.orElse(Some(p)))
+ case Some(p @ SplitCallIntoParts(`tree`, _)) =>
+ findLastApplyAndNextSelectEnclosed(p, select)
+ case _ => (tree, select)
+ }
+
+ @tailrec
+ private def findLastApplyAndNextSelectPastEnclosed(
+ tree: Tree,
+ select: Option[Term.Select] = None,
+ prevEnclosed: Option[Tree] = None
): (Tree, Option[Term.Select]) =
tree.parent match {
case Some(p: Term.Select) =>
- findLastApplyAndNextSelect(p, select.orElse(Some(p)))
+ findLastApplyAndNextSelectPastEnclosed(p, select.orElse(Some(p)))
case Some(p @ SplitCallIntoParts(`tree`, _)) =>
- if (isEnclosedInMatching(p)) (p, select)
- else findLastApplyAndNextSelect(p, select)
- case _ => (tree, select)
+ prevEnclosed match {
+ case Some(t) => (t, select)
+ case _ =>
+ val nextEnclosed =
+ if (isEnclosedInMatching(tree)) Some(tree) else None
+ findLastApplyAndNextSelectPastEnclosed(p, select, nextEnclosed)
+ }
+ case _ => (prevEnclosed.getOrElse(tree), select)
+ }
+
+ final def findLastApplyAndNextSelect(
+ tree: Tree,
+ enclosed: Boolean
+ ): (Tree, Option[Term.Select]) =
+ if (enclosed) findLastApplyAndNextSelectEnclosed(tree)
+ else findLastApplyAndNextSelectPastEnclosed(tree)
+
+ def canStartSelectChain(
+ thisSelect: Term.Select,
+ nextSelect: Option[Term.Select],
+ lastApply: Tree
+ )(implicit style: ScalafmtConfig): Boolean = {
+ val ok = (thisSelect ne lastApply) && !cannotStartSelectChain(thisSelect)
+ ok && (thisSelect.parent match {
+ case `nextSelect` => style.includeNoParensInSelectChains
+ case Some(ta: Term.Apply)
+ if ta.args.lengthCompare(1) == 0 &&
+ nextNonComment(tokens(ta.fun.tokens.last)).right.is[T.LeftBrace] =>
+ style.includeCurlyBraceInSelectChains &&
+ !nextSelect.contains(lastApply) // exclude short curly
+ case Some(SplitCallIntoParts(`thisSelect`, _)) => true
+ case _ => false
+ })
+ }
+
+ /** Checks if an earlier select started the chain */
+ @tailrec
+ final def inSelectChain(
+ prevSelect: Option[Term.Select],
+ thisSelect: Term.Select,
+ lastApply: Tree
+ )(implicit style: ScalafmtConfig): Boolean =
+ prevSelect match {
+ case None => false
+ case Some(p) if canStartSelectChain(p, Some(thisSelect), lastApply) =>
+ true
+ case Some(p) =>
+ val prevPrevSelect = findPrevSelect(p, style.encloseSelectChains)
+ inSelectChain(prevPrevSelect, p, lastApply)
}
@tailrec
@@ -1441,4 +1461,41 @@ class FormatOps(val tree: Tree, baseStyle: ScalafmtConfig) {
)(f: FormatToken => Boolean): Either[FormatToken, FormatToken] =
findTokenWith(ft, iter)(Some(_).filter(f))
+ @tailrec
+ final def findXmlLastLineIndent(ft: FormatToken): Int =
+ ft.left match {
+ case _: Token.Xml.Start => 0
+ case t: Token.Xml.Part =>
+ TokenOps.getXmlLastLineIndent(t) match {
+ case Some(x) => x
+ case None => findXmlLastLineIndent(prev(ft))
+ }
+ case t: Token.Xml.SpliceEnd =>
+ findXmlLastLineIndent(tokens(matching(t), -1))
+ case _ =>
+ findXmlLastLineIndent(prev(ft))
+ }
+
+ def withIndentOnXmlStart(tok: T.Xml.Start, splits: Seq[Split])(implicit
+ style: ScalafmtConfig
+ ): Seq[Split] = {
+ if (style.xmlLiterals.assumeFormatted) {
+ val end = matching(tok)
+ val indent = Num(findXmlLastLineIndent(tokens(end, -1)), true)
+ splits.map(_.withIndent(indent, end, ExpiresOn.After))
+ } else splits
+ }
+
+ def withIndentOnXmlSpliceStart(ft: FormatToken, splits: Seq[Split])(implicit
+ style: ScalafmtConfig
+ ): Seq[Split] = {
+ ft.left match {
+ case t: T.Xml.SpliceStart if style.xmlLiterals.assumeFormatted =>
+ val end = matching(t)
+ val indent = Num(findXmlLastLineIndent(prev(ft)), true)
+ splits.map(_.withIndent(indent, end, ExpiresOn.After))
+ case _ => splits
+ }
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatToken.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatToken.scala
index 8bc84758e3..ab7c3b78af 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatToken.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatToken.scala
@@ -19,7 +19,7 @@ import org.scalafmt.util.TokenOps._
case class FormatToken(left: Token, right: Token, meta: FormatToken.Meta) {
override def toString =
- s"${left.syntax}∙${right.syntax}[${left.end}:${right.end}]"
+ s"${meta.left.text}∙${meta.right.text}[${left.end}:${right.end}]"
def inside(range: Set[Range]): Boolean = {
if (range.isEmpty) true
@@ -27,12 +27,13 @@ case class FormatToken(left: Token, right: Token, meta: FormatToken.Meta) {
}
def between = meta.between
+ lazy val betweenText: String = between.map(_.syntax).mkString
lazy val newlinesBetween: Int = meta.between.count(_.is[Token.LF])
@inline def noBreak: Boolean = FormatToken.noBreak(newlinesBetween)
@inline def hasBreak: Boolean = newlinesBetween != 0
@inline def hasBlankLine: Boolean = FormatToken.hasBlankLine(newlinesBetween)
- val leftHasNewline = left.syntax.contains('\n')
+ @inline def leftHasNewline = meta.left.firstNL >= 0
/**
* A format token is uniquely identified by its left token.
@@ -52,8 +53,18 @@ object FormatToken {
case class Meta(
between: Array[Token],
idx: Int,
- leftOwner: Tree,
- rightOwner: Tree
- )
+ left: TokenMeta,
+ right: TokenMeta
+ ) {
+ @inline def leftOwner: Tree = left.owner
+ @inline def rightOwner: Tree = right.owner
+ }
+
+ case class TokenMeta(
+ owner: Tree,
+ text: String
+ ) {
+ lazy val firstNL = text.indexOf('\n')
+ }
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala
index af325c07ef..d48ad26a42 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatTokens.scala
@@ -44,7 +44,7 @@ object FormatTokens {
*/
def apply(tokens: Tokens, owner: Token => Tree): FormatTokens = {
var left = tokens.head
- var lowner = owner(left)
+ var lmeta = FormatToken.TokenMeta(owner(left), left.syntax)
val result = Array.newBuilder[FormatToken]
var ftIdx = 0
var wsIdx = 0
@@ -53,12 +53,12 @@ object FormatTokens {
arr.foreach {
case Whitespace() => tokIdx += 1
case right =>
- val rowner = owner(right)
+ val rmeta = FormatToken.TokenMeta(owner(right), right.syntax)
val meta =
- FormatToken.Meta(arr.slice(wsIdx, tokIdx), ftIdx, lowner, rowner)
+ FormatToken.Meta(arr.slice(wsIdx, tokIdx), ftIdx, lmeta, rmeta)
result += FormatToken(left, right, meta)
left = right
- lowner = rowner
+ lmeta = rmeta
ftIdx += 1
tokIdx += 1
wsIdx = tokIdx
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala
index 3e4ce0d432..a48ef2b727 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala
@@ -1,14 +1,19 @@
package org.scalafmt.internal
+import java.nio.CharBuffer
import java.util.regex.Pattern
-import org.scalafmt.config.ScalafmtConfig
+import org.scalafmt.CompatCollections.JavaConverters._
+import org.scalafmt.config.{Comments, Docstrings, ScalafmtConfig}
import org.scalafmt.rewrite.RedundantBraces
import org.scalafmt.util.TokenOps._
import org.scalafmt.util.{LiteralOps, TreeOps}
import scala.annotation.tailrec
+import scala.collection.AbstractIterator
import scala.collection.mutable
+import scala.meta.internal.Scaladoc
+import scala.meta.internal.parsers.ScaladocParser
import scala.meta.tokens.Token
import scala.meta.tokens.{Token => T}
import scala.meta.transversers.Traverser
@@ -34,7 +39,7 @@ class FormatWriter(formatOps: FormatOps) {
import formatOps._
def mkString(state: State): String = {
- val sb = new StringBuilder()
+ implicit val sb = new StringBuilder()
val locations = getFormatLocations(state)
locations.iterate.foreach { entry =>
@@ -47,26 +52,27 @@ class FormatWriter(formatOps: FormatOps) {
// formatting flag fetches from the previous state because of
// `formatToken.left` rendering. `FormatToken(x, // format: on)` will have
// formatOff = false, but x still should not be formatted
- case token if state.formatOff => sb.append(token.syntax)
- case c: T.Comment =>
- sb.append(formatComment(c, state.indentation))
+ case _ if state.formatOff => sb.append(formatToken.meta.left.text)
+ case _: T.Comment =>
+ entry.formatComment
case _: T.Interpolation.Part | _: T.Constant.String =>
sb.append(entry.formatMarginized)
case c: T.Constant.Int =>
- sb.append(LiteralOps.prettyPrintInteger(c.syntax))
+ sb.append(LiteralOps.prettyPrintInteger(formatToken.meta.left.text))
case c: T.Constant.Long =>
- sb.append(LiteralOps.prettyPrintInteger(c.syntax))
+ sb.append(LiteralOps.prettyPrintInteger(formatToken.meta.left.text))
case c: T.Constant.Float =>
- sb.append(LiteralOps.prettyPrintFloat(c.syntax))
+ sb.append(LiteralOps.prettyPrintFloat(formatToken.meta.left.text))
case c: T.Constant.Double =>
- sb.append(LiteralOps.prettyPrintDouble(c.syntax))
+ sb.append(LiteralOps.prettyPrintDouble(formatToken.meta.left.text))
case token =>
- val syntax = Option(location.replace).getOrElse(token.syntax)
+ val syntax =
+ Option(location.replace).getOrElse(formatToken.meta.left.text)
val rewrittenToken = style.rewriteTokens.getOrElse(syntax, syntax)
sb.append(rewrittenToken)
}
- entry.formatWhitespace(sb, state.formatOff)
+ entry.formatWhitespace
}
sb.toString()
@@ -83,7 +89,7 @@ class FormatWriter(formatOps: FormatOps) {
val prev = state.prev
val idx = prev.depth
val ft = toks(idx)
- val breaks = ft.leftHasNewline || state.split.modification.isNewline
+ val breaks = state.split.isNL || ft.leftHasNewline
val newLineId = lineId + (if (breaks) 1 else 0)
result(idx) = FormatLocation(ft, state, styleMap.at(ft), newLineId)
iter(prev, newLineId)
@@ -128,12 +134,12 @@ class FormatWriter(formatOps: FormatOps) {
val inParentheses = loc.style.spaces.inParentheses
// remove space before "{"
val prevBegState =
- if (0 == idx || (state.prev.split.modification ne Space))
+ if (0 == idx || (state.prev.split.modExt.mod ne Space))
state.prev
else {
val prevloc = locations(idx - 1)
- val prevState = state.prev
- .copy(split = state.prev.split.copy(modification = NoSplit))
+ val prevState =
+ state.prev.copy(split = state.prev.split.withMod(NoSplit))
locations(idx - 1) = prevloc.copy(
shift = prevloc.shift - 1,
state = prevState
@@ -150,7 +156,7 @@ class FormatWriter(formatOps: FormatOps) {
)
else {
// remove space after "{"
- val split = state.split.copy(modification = NoSplit)
+ val split = state.split.withMod(NoSplit)
loc.copy(
replace = "(",
shift = loc.shift - 1,
@@ -164,7 +170,7 @@ class FormatWriter(formatOps: FormatOps) {
if (inParentheses) prevEndState
else {
// remove space before "}"
- val split = prevEndState.split.copy(modification = NoSplit)
+ val split = prevEndState.split.withMod(NoSplit)
val newState = prevEndState.copy(split = split)
locations(end - 1) = prevEndLoc
.copy(shift = prevEndLoc.shift - 1, state = newState)
@@ -203,18 +209,10 @@ class FormatWriter(formatOps: FormatOps) {
@inline def tok = curr.formatToken
@inline def state = curr.state
@inline def prevState = curr.state.prev
- @inline def lastModification = prevState.split.modification
+ @inline def lastModification = prevState.split.modExt.mod
def getWhitespace(alignOffset: Int): String = {
- // TODO this could get slow for really long comment blocks. If that
- // becomes a problem, we could also precompute these locations.
- def nextNonComment = {
- val nonCommentIdx =
- locations.indexWhere(!_.formatToken.right.is[T.Comment], i + 1)
- if (0 > nonCommentIdx) None else Some(locations(nonCommentIdx))
- }
-
- state.split.modification match {
+ state.split.modExt.mod match {
case Space =>
val previousAlign =
if (lastModification != NoSplit) 0
@@ -222,27 +220,6 @@ class FormatWriter(formatOps: FormatOps) {
val currentAlign = getAlign(tok, alignOffset)
getIndentation(1 + currentAlign + previousAlign)
- case nl: NewlineT
- if nl.acceptNoSplit && !tok.left.isInstanceOf[T.Comment] &&
- state.indentation >= prevState.column =>
- ""
-
- case nl: NewlineT
- if nl.acceptSpace && state.indentation >= prevState.column =>
- " "
-
- case _: NewlineT
- if tok.right.isInstanceOf[T.Comment] &&
- nextNonComment.exists(
- _.formatToken.right.isInstanceOf[T.Dot]
- ) =>
- // TODO this could slow for really long chains and could be indexed if necessary.
- val prevDotIdx =
- locations.lastIndexWhere(_.formatToken.right.is[T.Dot], i - 1)
- // TODO should this 2 be hard-coded, set to some other existing configurable parameter, or configurable?
- val extraIndent = if (0 <= prevDotIdx) 0 else 2
- "\n" + getIndentation(state.indentation + extraIndent)
-
case nl: NewlineT =>
val isDouble = nl.isDouble ||
style.newlines.forceBlankBeforeMultilineTopLevelStmt &&
@@ -256,13 +233,13 @@ class FormatWriter(formatOps: FormatOps) {
if (nl.noIndent) newline
else newline + getIndentation(state.indentation)
- case Provided(literal) => literal
+ case Provided(ft) => ft.betweenText
case NoSplit => ""
}
}
- def formatWhitespace(sb: StringBuilder, formatOff: Boolean): Unit = {
+ def formatWhitespace(implicit sb: StringBuilder): Unit = {
import org.scalafmt.config.TrailingCommas
@@ -283,7 +260,7 @@ class FormatWriter(formatOps: FormatOps) {
val right = nextNonCommentTok.right
def isNewline =
Seq(curr, locations(math.min(i + skip, locations.length - 1)))
- .exists(_.state.split.modification.isNewline)
+ .exists(_.state.split.isNL)
// Scala syntax allows commas before right braces in weird places,
// like constructor bodies:
@@ -316,7 +293,7 @@ class FormatWriter(formatOps: FormatOps) {
val noExtraOffset =
!runner.dialect.allowTrailingCommas ||
tok.left.is[T.Comment] ||
- formatOff
+ prevState.formatOff
if (noExtraOffset)
ws(0)
@@ -344,14 +321,14 @@ class FormatWriter(formatOps: FormatOps) {
}
def formatMarginized: String = {
- val text = tok.left.syntax
+ val text = tok.meta.left.text
val tupleOpt = tok.left match {
case _ if !style.assumeStandardLibraryStripMargin => None
+ case _ if tok.meta.left.firstNL < 0 => None
case _: T.Constant.String =>
TreeOps.getStripMarginChar(tok.meta.leftOwner).map { pipe =>
def isPipeFirstChar = text.find(_ != '"').contains(pipe)
- val noAlign = !style.align.stripMargin ||
- prevState.split.modification.isNewline
+ val noAlign = !style.align.stripMargin || prevState.split.isNL
val pipeOffset =
if (style.align.stripMargin && isPipeFirstChar) 1 else 0
val indent = pipeOffset +
@@ -367,8 +344,9 @@ class FormatWriter(formatOps: FormatOps) {
val indent =
if (!style.align.stripMargin) tiState.indentation
else
- tiState.column + (ti.args.headOption match {
- case Some(Lit.String(x)) if x(0) == pipe => 1
+ tiState.column + (ti.parts.headOption match {
+ case Some(Lit.String(x)) if x.headOption.contains(pipe) =>
+ 1
case _ => 0
})
(pipe, 2 + indent)
@@ -379,12 +357,388 @@ class FormatWriter(formatOps: FormatOps) {
tupleOpt.fold(text) {
case (pipe, indent) =>
val spaces = getIndentation(indent)
- getStripMarginPattern(pipe)
- .matcher(text)
- .replaceAll(s"\n${spaces}$$1")
+ getStripMarginPattern(pipe).matcher(text).replaceAll(spaces)
+ }
+ }
+
+ def formatComment(implicit sb: StringBuilder): Unit = {
+ val text = tok.meta.left.text
+ if (isSingleLineComment(text))
+ new FormatSlc(text).format
+ else if (text == "/**/")
+ sb.append(text)
+ else if (isDocstring(text))
+ formatDocstring(text)
+ else
+ new FormatMlc(text).format
+ }
+
+ private def formatOnelineDocstring(
+ text: String
+ )(implicit sb: StringBuilder): Boolean = {
+ curr.isStandalone && {
+ val matcher = onelineDocstring.matcher(text)
+ matcher.matches() && (style.docstrings.oneline match {
+ case Docstrings.Oneline.fold => true
+ case Docstrings.Oneline.unfold => false
+ case Docstrings.Oneline.keep =>
+ matcher.start(1) == -1 && matcher.start(3) == -1
+ }) && {
+ val content = matcher.group(2)
+ val folding = // 7 is the length of "/** " and " */"
+ content.length <= style.maxColumn - prevState.indentation - 7 ||
+ (style.docstrings.wrap eq Docstrings.Wrap.no)
+ if (folding) sb.append("/** ").append(content).append(" */")
+ folding
+ }
+ }
+ }
+
+ private def formatDocstring(
+ text: String
+ )(implicit sb: StringBuilder): Unit = {
+ if (style.docstrings.style.isEmpty) sb.append(text)
+ else if (!formatOnelineDocstring(text))
+ new FormatMlDoc(text).format
+ }
+
+ private abstract class FormatCommentBase(
+ protected val extraIndent: Int = 1,
+ protected val leadingMargin: Int = 0
+ )(implicit sb: StringBuilder) {
+ protected final val breakBefore = curr.hasBreakBefore
+ protected final val indent =
+ if (breakBefore) prevState.indentation
+ else prevState.prev.indentation
+ // extra 1 is for "*" (in "/*" or " *") or "/" (in "//")
+ protected final val maxLength =
+ style.maxColumn - indent - extraIndent - 1
+
+ protected final def getFirstLineLength =
+ if (breakBefore) 0
+ else
+ prevState.prev.column - prevState.prev.indentation +
+ prevState.split.length
+
+ protected final def canRewrite =
+ style.comments.wrap match {
+ case Comments.Wrap.no => false
+ case Comments.Wrap.trailing => curr.hasBreakAfter
+ case Comments.Wrap.standalone => breakBefore && curr.hasBreakAfter
+ }
+
+ protected final type WordIter = Iterator[String]
+ @tailrec
+ protected final def iterWords(
+ iter: WordIter,
+ appendLineBreak: () => Unit,
+ lineLength: Int = 0,
+ extraMargin: String = " "
+ ): Unit =
+ if (iter.hasNext) {
+ val word = iter.next()
+ val length = word.length
+ val maybeNextLineLength = 1 + length +
+ (if (lineLength == 0) leadingMargin else lineLength)
+ val nextLineLength =
+ if (
+ lineLength < extraMargin.length ||
+ maybeNextLineLength <= maxLength
+ ) {
+ sb.append(' ')
+ maybeNextLineLength
+ } else {
+ appendLineBreak()
+ sb.append(extraMargin)
+ length + extraMargin.length
+ }
+ sb.append(word)
+ iterWords(iter, appendLineBreak, nextLineLength, extraMargin)
+ }
+ }
+
+ private class FormatSlc(text: String)(implicit sb: StringBuilder)
+ extends FormatCommentBase {
+ def format: Unit = {
+ if (
+ canRewrite &&
+ text.length + indent > style.maxColumn
+ ) {
+ val useSlc =
+ breakBefore && style.comments.wrapStandaloneSlcAsSlc
+ val appendLineBreak: () => Unit =
+ if (useSlc) {
+ val spaces: String = getIndentation(indent)
+ () => sb.append('\n').append(spaces).append("//")
+ } else {
+ val spaces: String = getIndentation(indent + 1)
+ () => sb.append('\n').append(spaces).append('*')
+ }
+ val contents = text.substring(2).trim
+ val wordIter = splitAsIterator(slcDelim)(contents)
+ sb.append(if (useSlc) "//" else "/*")
+ iterWords(wordIter, appendLineBreak, getFirstLineLength)
+ if (!useSlc) sb.append(" */")
+ } else sb.append(removeTrailingWhiteSpace(text))
}
}
+ private class FormatMlc(text: String)(implicit sb: StringBuilder)
+ extends FormatCommentBase {
+ private val spaces: String = getIndentation(indent + 1)
+
+ def format: Unit = {
+ // don't rewrite comments which contain nested comments
+ if (canRewrite && text.lastIndexOf("/*") == 0) {
+ val sectionIter = new SectIter {
+ private val lineIter = {
+ val header = mlcHeader.matcher(text)
+ val beg = if (header.lookingAt()) header.end() else 2
+ val contents = text.substring(beg, text.length - 2)
+ splitAsIterator(mlcLineDelim)(contents).buffered
+ }
+ private def paraEnds: Boolean = lineIter.head.isEmpty
+
+ override def hasNext = lineIter.hasNext
+ override def next() = new ParaIter
+
+ class ParaIter extends AbstractIterator[WordIter] {
+ private var hasPara: Boolean = true
+ override def hasNext: Boolean =
+ hasPara && lineIter.hasNext && {
+ hasPara = !paraEnds
+ if (!hasPara)
+ do lineIter.next() while (lineIter.hasNext && paraEnds)
+ hasPara
+ }
+ override def next() =
+ new ParaLineIter().flatMap(splitAsIterator(slcDelim))
+
+ class ParaLineIter extends AbstractIterator[String] {
+ private var hasLine: Boolean = true
+ override def hasNext: Boolean = hasLine
+ override def next(): String = {
+ val head = lineIter.next()
+ hasLine = lineIter.hasNext && !paraEnds &&
+ !mlcParagraphEnd.matcher(head).find() &&
+ !mlcParagraphBeg.matcher(lineIter.head).find()
+ head
+ }
+ }
+ }
+ }
+ sb.append("/*")
+ iterSections(sectionIter, getFirstLineLength)
+ sb.append(" */")
+ } else {
+ val trimmed = removeTrailingWhiteSpace(text)
+ sb.append(leadingAsteriskSpace.matcher(trimmed).replaceAll(spaces))
+ }
+ }
+
+ private def appendLineBreak(): Unit = {
+ sb.append('\n').append(spaces).append('*')
+ }
+
+ private type ParaIter = Iterator[WordIter]
+ private def iterParagraphs(iter: ParaIter, firstLineLen: Int): Unit = {
+ iterWords(iter.next(), appendLineBreak, firstLineLen)
+ while (iter.hasNext) {
+ appendLineBreak()
+ iterWords(iter.next(), appendLineBreak)
+ }
+ }
+
+ private type SectIter = Iterator[ParaIter]
+ private def iterSections(iter: SectIter, firstLineLen: Int): Unit = {
+ iterParagraphs(iter.next(), firstLineLen)
+ while (iter.hasNext) {
+ appendLineBreak()
+ appendLineBreak()
+ iterParagraphs(iter.next(), 0)
+ }
+ }
+ }
+
+ private class FormatMlDoc(text: String)(implicit sb: StringBuilder)
+ extends FormatCommentBase(
+ if (style.docstrings.isSpaceAsterisk) 2 else 1,
+ if (style.docstrings.isAsteriskSpace) 1 else 0
+ ) {
+ private val spaces: String = getIndentation(indent + extraIndent)
+ private val margin = getIndentation(1 + leadingMargin)
+
+ def format: Unit = {
+ val docOpt =
+ if (
+ (style.docstrings.wrap eq Docstrings.Wrap.yes) &&
+ curr.isStandalone
+ )
+ ScaladocParser.parse(tok.meta.left.text)
+ else None
+ docOpt.fold(formatNoWrap)(formatWithWrap)
+ }
+
+ private def formatWithWrap(doc: Scaladoc): Unit = {
+ sb.append("/**")
+ if (style.docstrings.skipFirstLine) appendBreak()
+ sb.append(' ')
+ val sbLen = sb.length()
+ val paras = doc.para.iterator
+ paras.foreach { para =>
+ para.term.foreach { term =>
+ if (sb.length() != sbLen) sb.append(margin)
+ term match {
+ case t: Scaladoc.CodeBlock =>
+ sb.append("{{{")
+ appendBreak()
+ t.code.foreach { x =>
+ if (x.nonEmpty) {
+ val matcher = docstringLeadingSpace.matcher(x)
+ val minMargin = margin.length
+ if (matcher.lookingAt()) {
+ val offset = matcher.end()
+ val extra = math.max(0, offset - minMargin)
+ val codeIndent = minMargin + extra - extra % 2
+ sb.append(getIndentation(codeIndent))
+ sb.append(CharBuffer.wrap(x, offset, x.length))
+ } else
+ sb.append(getIndentation(minMargin)).append(x)
+ }
+ appendBreak()
+ }
+ sb.append(margin).append("}}}")
+ appendBreak()
+ case t: Scaladoc.Heading =>
+ val delimiter = t.level * '='
+ sb.append(delimiter).append(t.title).append(delimiter)
+ appendBreak()
+ case t: Scaladoc.Tag =>
+ sb.append(t.tag.tag)
+ if (t.tag.hasLabel) sb.append(' ').append(t.label.syntax)
+ if (t.tag.hasDesc) {
+ val words = t.desc.part.iterator.map(_.syntax)
+ val tagMargin = getIndentation(2 + margin.length)
+ // use maxLength to force a newline
+ iterWords(words, appendBreak, maxLength, tagMargin)
+ }
+ appendBreak()
+ case t: Scaladoc.ListBlock =>
+ // outputs margin space and appends new line, too
+ // therefore, let's start by "rewinding"
+ if (sb.length() != sbLen || leadingMargin == 0) {
+ sb.setLength(sb.length() - margin.length)
+ } else {
+ // don't output on top line, lists are sensitive to margin
+ sb.setLength(sb.length() - 1) // remove space
+ appendBreak()
+ }
+ formatListBlock(getIndentation(margin.length + 2))(t)
+ case t: Scaladoc.Text =>
+ formatTextAfterMargin(t.part.iterator.map(_.syntax))
+ case t: Scaladoc.Table => formatTable(t)
+ }
+ }
+ if (paras.hasNext) appendBreak()
+ }
+ sb.append('/')
+ }
+
+ private def formatTextAfterMargin(words: WordIter): Unit = {
+ // remove space as iterWords adds it
+ sb.setLength(sb.length() - 1)
+ iterWords(words, appendBreak, 0, margin)
+ appendBreak()
+ }
+
+ private def formatListBlock(
+ listIndent: String
+ )(block: Scaladoc.ListBlock): Unit = {
+ val prefix = block.prefix
+ val itemIndent = getIndentation(listIndent.length + prefix.length + 1)
+ block.item.foreach { x =>
+ sb.append(listIndent).append(prefix)
+ formatListTerm(itemIndent)(x)
+ }
+ }
+
+ private def formatListTerm(
+ itemIndent: String
+ )(item: Scaladoc.ListItem): Unit = {
+ val words = item.text.part.iterator.map(_.syntax)
+ iterWords(words, appendBreak, itemIndent.length - 1, itemIndent)
+ appendBreak()
+ item.nested.foreach(formatListBlock(itemIndent))
+ }
+
+ private def formatTable(table: Scaladoc.Table): Unit = {
+ val rows = table.row.view :+ table.header
+ val align = table.align
+ val maxCols = rows.map(_.col.length).max
+ val colsRange = 0 until maxCols
+ val maxLengths = colsRange.map { x =>
+ rows.collect { case r if r.col.length > x => r.col(x).length }.max
+ }
+
+ @inline def beforeAll: Unit = sb.append(margin)
+ @inline def beforeEach: Unit = sb.append('|')
+ @inline def afterAll: Unit = { sb.append('|'); appendBreak() }
+ @inline def getAlign(col: Int) =
+ if (col < align.length) align(col) else Scaladoc.Table.Left
+ def formatCols(useMargin: Boolean)(f: Int => Unit): Unit = {
+ if (useMargin) beforeAll
+ colsRange.foreach { x => beforeEach; f(x) }
+ afterAll
+ }
+
+ def formatRow(useMargin: Boolean)(row: Scaladoc.Table.Row): Unit =
+ formatCols(useMargin) { col =>
+ val cell = if (col < row.col.length) row.col(col) else ""
+ val pad = maxLengths(col) - cell.length
+ val lpad = getAlign(col).leftPad(pad)
+ sb.append(getIndentation(1 + lpad))
+ .append(cell)
+ .append(getIndentation(1 + pad - lpad))
+ }
+ formatRow(false)(table.header)
+ formatCols(true) { col =>
+ sb.append(getAlign(col).syntax(maxLengths(col)))
+ }
+ table.row.foreach(formatRow(true))
+ }
+
+ private def formatNoWrap: Unit = {
+ // remove "/*" (keep one asterisk) and "*/"
+ val trimmed = CharBuffer.wrap(text, 2, text.length - 2)
+ val matcher = docstringLine.matcher(trimmed)
+ sb.append("/**")
+ val sbLen = sb.length()
+ var prevWasBlank = style.docstrings.skipFirstLine
+ while (matcher.find()) {
+ val contentBeg = matcher.start(2)
+ val contentEnd = matcher.end(2)
+ if (contentBeg == contentEnd) prevWasBlank = true
+ else {
+ if (sb.length() != sbLen) appendBreak()
+ if (prevWasBlank) {
+ appendBreak
+ prevWasBlank = false
+ }
+ if (sb.length() == sbLen) sb.append(' ') else sb.append(margin)
+ val extraMargin =
+ matcher.end(1) - matcher.start(1) - margin.length
+ if (extraMargin > 0) sb.append(getIndentation(extraMargin))
+ sb.append(CharBuffer.wrap(trimmed, contentBeg, contentEnd))
+ }
+ }
+ appendBreak
+ sb.append('/')
+ }
+
+ private def appendBreak(): Unit =
+ sb.append('\n').append(spaces).append('*')
+ }
+
}
/**
@@ -426,7 +780,7 @@ class FormatWriter(formatOps: FormatOps) {
def processLine: FormatLocation = {
val floc = locations(idx)
idx += 1
- val ok = !floc.state.split.modification.isNewline
+ val ok = !floc.state.split.isNL
if (!ok || floc.formatToken.leftHasNewline) columnShift = 0
columnShift += floc.shift
if (!ok) floc
@@ -446,7 +800,7 @@ class FormatWriter(formatOps: FormatOps) {
}
implicit val location = processLine
- val doubleNewline = location.state.split.modification.newlines > 1
+ val doubleNewline = location.state.split.modExt.mod.newlines > 1
if (alignContainer eq null) {
getBlockToFlush(
getAlignContainer(location.formatToken.meta.rightOwner),
@@ -559,7 +913,7 @@ class FormatWriter(formatOps: FormatOps) {
def getAlignContainer(implicit location: FormatLocation): Option[Tree] = {
val ft = location.formatToken
val slc = isSingleLineComment(ft.right)
- val code = if (slc) "//" else ft.right.syntax
+ val code = if (slc) "//" else ft.meta.right.text
location.style.alignMap.get(code).flatMap { pattern =>
val owner = getAlignOwner(ft)
@@ -602,7 +956,7 @@ class FormatWriter(formatOps: FormatOps) {
* "io.get-coursier" % "interface" % "0.0.17"
* )
* ```
- * */
+ */
val previousSeparatorLengthGaps = new Array[Int](block.length)
while (column < columns) {
val alignmentUnits = prepareAlignmentInfo(
@@ -628,15 +982,16 @@ class FormatWriter(formatOps: FormatOps) {
private def shiftStateColumnIndent(startIdx: Int, offset: Int): Unit = {
// look for StateColumn; it returns indent=0 for withStateOffset(0)
- val stateIndentOpt = locations(startIdx).state.split.indents
- .flatMap(_.withStateOffset(0).filter(_.length == 0))
+ val stateIndentOpt = locations(startIdx).state.split.modExt.indents
+ .filter(_.hasStateColumn)
+ .flatMap(_.withStateOffset(0))
stateIndentOpt.headOption.foreach { indent =>
@tailrec
def updateLocation(idx: Int): Unit = {
val floc = locations(idx)
if (indent.notExpiredBy(floc.formatToken)) {
val state = floc.state
- if (state.split.modification.isNewline) {
+ if (state.split.isNL) {
locations(idx) = floc.copy(state =
state.copy(indentation = state.indentation + offset)
)
@@ -696,7 +1051,7 @@ class FormatWriter(formatOps: FormatOps) {
if (minLines <= 0) true
else if (i >= toks.length || toks(i).formatToken.left == end) false
else {
- val hasNL = toks(i).state.split.modification.isNewline
+ val hasNL = toks(i).state.split.isNL
isMultiline(end, i + 1, if (hasNL) minLines - 1 else minLines)
}
val formatToken = toks(i).formatToken
@@ -826,7 +1181,7 @@ class FormatWriter(formatOps: FormatOps) {
val separatorLength =
if (location.formatToken.right.is[Token.Comment]) 0
else if (!initStyle.activeForEdition_2020_03) 0
- else location.formatToken.right.syntax.length
+ else location.formatToken.meta.right.text.length
units += AlignmentUnit(
key + separatorLength,
location.formatToken.meta.idx,
@@ -855,7 +1210,12 @@ object FormatWriter {
alignContainer: Tree = null,
alignHashKey: Int = 0,
replace: String = null
- )
+ ) {
+ def hasBreakAfter: Boolean = state.split.isNL
+ def hasBreakBefore: Boolean =
+ state.prev.split.isNL || formatToken.left.start == 0
+ def isStandalone: Boolean = hasBreakAfter && hasBreakBefore
+ }
/**
* Alignment information extracted from FormatToken. Used only when align!=none.
@@ -872,7 +1232,7 @@ object FormatWriter {
* hash("io.get-coursier") => tokenHash
* length(%) => separatorLength
* line number in block (1) => lineIndex
- * */
+ */
case class AlignmentUnit(
width: Int,
ftIndex: Int,
@@ -894,33 +1254,30 @@ object FormatWriter {
private def getIndentation(len: Int): String =
if (len < indentations.length) indentations(len) else " " * len
- private val trailingSpace = Pattern.compile("\\h+$", Pattern.MULTILINE)
+ private val trailingSpace = Pattern.compile("\\h++$", Pattern.MULTILINE)
private def removeTrailingWhiteSpace(str: String): String = {
trailingSpace.matcher(str).replaceAll("")
}
- private val leadingAsteriskSpace = Pattern.compile("\n\\h*\\*([^*])")
-
- private def formatComment(
- comment: T.Comment,
- indent: Int
- )(implicit style: ScalafmtConfig): String = {
- val alignedComment =
- if (
- comment.syntax.startsWith("/*") &&
- style.reformatDocstrings
- ) {
- val isDocstring = comment.syntax.startsWith("/**") && style.scalaDocs
- val spaces: String =
- getIndentation(if (isDocstring) (indent + 2) else (indent + 1))
- leadingAsteriskSpace
- .matcher(comment.syntax)
- .replaceAll(s"\n${spaces}*$$1")
- } else {
- comment.syntax
- }
- removeTrailingWhiteSpace(alignedComment)
+ private def splitAsIterator(regex: Pattern)(value: String): Iterator[String] =
+ regex.splitAsStream(value).iterator().asScala
+
+ // "slc" stands for single-line comment
+ private val slcDelim = Pattern.compile("\\h++")
+ // "mlc" stands for multi-line comment
+ private val mlcHeader = Pattern.compile("^/\\*\\h*+(?:\n\\h*+[*]*+\\h*+)?")
+ private val mlcLineDelim = Pattern.compile("\\h*+\n\\h*+[*]*+\\h*+")
+ private val mlcParagraphEnd = Pattern.compile("[.:!?=]$")
+ private val mlcParagraphBeg = Pattern.compile("^(?:[-*@=]|\\d++[.:])")
+
+ private val leadingAsteriskSpace = Pattern.compile("(?<=\n)\\h*+(?=[*][^*])")
+ private val docstringLine =
+ Pattern.compile("^(?:\\h*+\\*)?(\\h*+)(.*?)\\h*+$", Pattern.MULTILINE)
+ private val onelineDocstring = {
+ val empty = "\\h*+(\n\\h*+\\*?\\h*+)*"
+ Pattern.compile(s"^/\\*\\*$empty([^*\n\\h](?:[^\n]*[^\n\\h])?)$empty\\*/$$")
}
+ private val docstringLeadingSpace = Pattern.compile("^\\h++")
@inline
private def getStripMarginPattern(pipe: Char) =
@@ -928,7 +1285,7 @@ object FormatWriter {
@inline
private def compileStripMarginPattern(pipe: Char) =
- Pattern.compile(s"\n\\h*(\\${pipe})")
+ Pattern.compile(s"(?<=\n)\\h*+(?=\\${pipe})")
private val leadingPipeSpace = compileStripMarginPattern('|')
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala
index af24f53747..5b51e4eb09 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Indent.scala
@@ -15,11 +15,12 @@ object ExpiresOn {
sealed abstract class Length {
def withStateOffset(offset: Int): Int
+ val reset: Boolean
}
object Length {
- case class Num(n: Int) extends Length {
+ case class Num(n: Int, reset: Boolean = false) extends Length {
override def withStateOffset(offset: Int): Int = n
override def toString: String = n.toString
}
@@ -34,10 +35,16 @@ object Length {
*/
case object StateColumn extends Length {
override def withStateOffset(offset: Int): Int = offset
+ override val reset: Boolean = false
}
}
-case class ActualIndent(length: Int, expire: Token, expiresAt: ExpiresOn) {
+case class ActualIndent(
+ length: Int,
+ expire: Token,
+ expiresAt: ExpiresOn,
+ reset: Boolean
+) {
def notExpiredBy(ft: FormatToken): Boolean = {
val expireToken: Token =
if (expiresAt == ExpiresOn.After) ft.left else ft.right
@@ -48,6 +55,7 @@ case class ActualIndent(length: Int, expire: Token, expiresAt: ExpiresOn) {
abstract class Indent {
def switch(switchObject: AnyRef): Indent
def withStateOffset(offset: Int): Option[ActualIndent]
+ def hasStateColumn: Boolean
}
/**
@@ -66,9 +74,17 @@ abstract class Indent {
*/
private class IndentImpl(length: Length, expire: Token, expiresAt: ExpiresOn)
extends Indent {
+ override def hasStateColumn: Boolean = length eq Length.StateColumn
override def switch(switchObject: AnyRef): Indent = this
override def withStateOffset(offset: Int): Option[ActualIndent] =
- Some(ActualIndent(length.withStateOffset(offset), expire, expiresAt))
+ Some(
+ ActualIndent(
+ length.withStateOffset(offset),
+ expire,
+ expiresAt,
+ length.reset
+ )
+ )
override def toString: String = {
val when = if (expiresAt == ExpiresOn.Before) '<' else '>'
s"$length$when$expire:${expire.end}"
@@ -79,13 +95,14 @@ object Indent {
def apply(length: Length, expire: Token, expiresAt: ExpiresOn): Indent =
length match {
- case Length.Num(0) => Empty
+ case Length.Num(0, _) => Empty
case x => new IndentImpl(x, expire, expiresAt)
}
case object Empty extends Indent {
override def withStateOffset(offset: Int): Option[ActualIndent] = None
override def switch(switchObject: AnyRef): Indent = this
+ override def hasStateColumn: Boolean = false
}
class Before(indent: Indent, before: AnyRef) extends Indent {
@@ -93,6 +110,7 @@ object Indent {
if (before ne switchObject) this else Indent.Empty
override def withStateOffset(offset: Int): Option[ActualIndent] =
indent.withStateOffset(offset)
+ override def hasStateColumn: Boolean = indent.hasStateColumn
override def toString: String = s"$indent>?"
}
@@ -100,7 +118,15 @@ object Indent {
override def switch(switchObject: AnyRef): Indent =
if (after ne switchObject) this else indent
override def withStateOffset(offset: Int): Option[ActualIndent] = None
+ override def hasStateColumn: Boolean = false
override def toString: String = s"?<$indent"
}
+ def getIndent(indents: Iterable[ActualIndent]): Int =
+ indents.foldLeft(0) {
+ case (res, elem) =>
+ if (elem.reset) elem.length
+ else res + elem.length
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala
new file mode 100644
index 0000000000..2811c7b163
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/ModExt.scala
@@ -0,0 +1,55 @@
+package org.scalafmt.internal
+
+import scala.language.implicitConversions
+import scala.meta.tokens.Token
+
+/**
+ * @param mod Is this a space, no space, newline or 2 newlines?
+ * @param indents Does this add indentation?
+ */
+case class ModExt(
+ mod: Modification,
+ indents: Seq[Indent] = Seq.empty
+) {
+ lazy val indentation = indents.mkString("[", ", ", "]")
+
+ def withIndent(length: => Length, expire: => Token, when: ExpiresOn): ModExt =
+ length match {
+ case Length.Num(0, _) => this
+ case x => withIndentImpl(Indent(x, expire, when))
+ }
+
+ def withIndentOpt(
+ length: => Length,
+ expire: Option[Token],
+ when: ExpiresOn
+ ): ModExt =
+ expire.fold(this)(withIndent(length, _, when))
+
+ def withIndent(indent: => Indent): ModExt =
+ indent match {
+ case Indent.Empty => this
+ case x => withIndentImpl(x)
+ }
+
+ def withIndentOpt(indent: => Option[Indent]): ModExt =
+ indent.fold(this)(withIndent(_))
+
+ def withIndents(indents: Seq[Indent]): ModExt =
+ indents.foldLeft(this)(_ withIndent _)
+
+ private def withIndentImpl(indent: Indent): ModExt =
+ copy(indents = indents :+ indent)
+
+ def switch(switchObject: AnyRef): ModExt = {
+ val newIndents = indents.map(_.switch(switchObject))
+ copy(indents = newIndents.filter(_ ne Indent.Empty))
+ }
+
+}
+
+object ModExt {
+
+ implicit def implicitModToModExt(mod: Modification): ModExt = ModExt(mod)
+
+}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala
index 241542cd9e..91aca117e8 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Modification.scala
@@ -6,13 +6,10 @@ sealed abstract class Modification {
@inline final def isNewline: Boolean = newlines != 0
}
-case class Provided(code: String) extends Modification {
- override lazy val newlines: Int = code.count(_ == '\n')
- override lazy val length: Int = {
- val firstLine = code.indexOf('\n')
- if (firstLine == -1) code.length
- else firstLine
- }
+case class Provided(ft: FormatToken) extends Modification {
+ override val newlines: Int = ft.newlinesBetween
+ override lazy val length: Int =
+ if (newlines == 0) ft.betweenText.length else ft.betweenText.indexOf('\n')
}
case object NoSplit extends Modification {
@@ -27,20 +24,21 @@ case object NoSplit extends Modification {
* @param isDouble Insert a blank line?
* @param noIndent Should no indentation follow? For example in commented out
* code.
- * @param acceptNoSplit Is it ok to replace this newline with a [[NoSplit]]
- * if the newline will indent beyond the current column?
- * For example, used by select chains in [[Router]].
+ * @param alt
+ * Is it ok to replace this newline with a [[NoSplit]] or [[Space]] (with
+ * an optional additional set of indents) if the newline will indent beyond
+ * the current column? For example, used by select chains in [[Router]].
*/
case class NewlineT(
isDouble: Boolean = false,
noIndent: Boolean = false,
- acceptSpace: Boolean = false,
- acceptNoSplit: Boolean = false
+ alt: Option[ModExt] = None
) extends Modification {
override def toString = {
val double = if (isDouble) "Double" else ""
val indent = if (noIndent) "NoIndent" else ""
- double + indent + "Newline"
+ val altStr = alt.fold("")(x => "|" + x.mod.toString)
+ double + indent + "Newline" + altStr
}
override val newlines: Int = if (isDouble) 2 else 1
override val length: Int = 0
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala
index ce8b965fee..ba0e25df6e 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Policy.scala
@@ -6,77 +6,205 @@ import scala.meta.tokens.Token
* The decision made by [[Router]].
*
* Used by [[Policy]] to enforce non-local formatting.
- *
- * @param f is applied to every decision until expire
- * @param expire The latest token offset.
*/
-case class Policy(
- f: Policy.Pf,
- expire: Int,
- noDequeue: Boolean = false
-)(implicit val line: sourcecode.Line) {
-
- def isEmpty: Boolean = Policy.isEmpty(f)
-
- def orElse(other: Policy.Pf, minExpire: Int = 0): Policy =
- if (Policy.isEmpty(other)) this
- else
- copy(
- f = if (isEmpty) other else f.orElse(other),
- expire = math.max(minExpire, expire)
- )
-
- def orElse(other: Policy): Policy =
- orElse(other.f, other.expire)
-
- def orElse(other: Option[Policy]): Policy =
- other.fold(this)(orElse)
-
- def andThen(other: Policy): Policy =
- if (isEmpty) other else andThen(other.f, other.expire)
-
- /** Similar to PartialFunction.andThen, except applies second pf even if the
- * first pf is not defined at argument.
- */
- def andThen(otherF: Policy.Pf, minExpire: Int = 0): Policy = {
- if (Policy.isEmpty(otherF)) this
- else if (isEmpty) copy(f = otherF)
- else {
- // TODO(olafur) optimize?
- val newPf: Policy.Pf = {
- case x =>
- otherF.applyOrElse(
- f.andThen(x.withSplits _).applyOrElse(x, identity[Decision]),
- (x: Decision) => x.splits
- )
- }
- copy(f = newPf, expire = math.max(minExpire, expire))
- }
- }
+abstract class Policy {
+
+ /** applied to every decision until expire */
+ def f: Policy.Pf
+
+ def exists(pred: Policy.Clause => Boolean): Boolean
+ def filter(pred: Policy.Clause => Boolean): Policy
+ def unexpired(ft: FormatToken): Policy
+ def noDequeue: Boolean
+
+ def &(other: Policy): Policy =
+ if (other.isEmpty) this else new Policy.AndThen(this, other)
+ def |(other: Policy): Policy =
+ if (other.isEmpty) this else new Policy.OrElse(this, other)
+
+ @inline
+ final def unexpiredOpt(ft: FormatToken): Option[Policy] =
+ Some(unexpired(ft)).filter(_.nonEmpty)
+
+ @inline
+ final def &(other: Option[Policy]): Policy = other.fold(this)(&)
+ @inline
+ final def |(other: Option[Policy]): Policy = other.fold(this)(|)
+
+ @inline
+ final def isEmpty: Boolean = this eq Policy.NoPolicy
+ @inline
+ final def nonEmpty: Boolean = this ne Policy.NoPolicy
- override def toString = s"P:${line.value}(D=$noDequeue)E:$expire"
}
object Policy {
type Pf = PartialFunction[Decision, Seq[Split]]
- val emptyPf: Pf = PartialFunction.empty
-
- val NoPolicy = new Policy(emptyPf, Integer.MAX_VALUE)(sourcecode.Line(0)) {
+ object NoPolicy extends Policy {
override def toString: String = "NoPolicy"
+ override def f: Pf = PartialFunction.empty
+ override def |(other: Policy): Policy = other
+ override def &(other: Policy): Policy = other
+
+ override def unexpired(ft: FormatToken): Policy = this
+ override def filter(pred: Clause => Boolean): Policy = this
+ override def exists(pred: Clause => Boolean): Boolean = false
+ override def noDequeue: Boolean = false
}
- def empty(token: Token)(implicit
- line: sourcecode.Line
- ): Policy = Policy(token)(emptyPf)
+ def apply(
+ endPolicy: End.WithPos,
+ noDequeue: Boolean = false
+ )(f: Pf)(implicit line: sourcecode.Line): Policy =
+ new ClauseImpl(f, endPolicy, noDequeue)
+
+ def after(
+ token: Token,
+ noDequeue: Boolean = false
+ )(f: Pf)(implicit line: sourcecode.Line): Policy =
+ apply(End.After(token), noDequeue)(f)
+
+ def before(
+ token: Token,
+ noDequeue: Boolean = false
+ )(f: Pf)(implicit line: sourcecode.Line): Policy =
+ apply(End.Before(token), noDequeue)(f)
+
+ def on(
+ token: Token,
+ noDequeue: Boolean = false
+ )(f: Pf)(implicit line: sourcecode.Line): Policy =
+ apply(End.On(token), noDequeue)(f)
+
+ abstract class Clause(implicit val line: sourcecode.Line) extends Policy {
+ val f: Policy.Pf
+ val endPolicy: End.WithPos
+
+ override def toString = {
+ val noDeqPrefix = if (noDequeue) "!" else ""
+ s"${line.value}$endPolicy${noDeqPrefix}d"
+ }
+
+ override def unexpired(ft: FormatToken): Policy =
+ if (endPolicy.notExpiredBy(ft)) this else NoPolicy
- def map(func: Token => Pf)(token: Token)(implicit
- line: sourcecode.Line
- ): Policy = Policy(token)(func(token))
+ override def filter(pred: Clause => Boolean): Policy =
+ if (pred(this)) this else NoPolicy
- def apply(token: Token)(f: Pf)(implicit line: sourcecode.Line): Policy =
- new Policy(f, token.end)
+ override def exists(pred: Clause => Boolean): Boolean = pred(this)
+ }
+
+ private class ClauseImpl(
+ val f: Policy.Pf,
+ val endPolicy: End.WithPos,
+ val noDequeue: Boolean
+ )(implicit line: sourcecode.Line)
+ extends Clause
+
+ private class OrElse(p1: Policy, p2: Policy) extends Policy {
+ override lazy val f: Pf = p1.f.orElse(p2.f)
+
+ override def unexpired(ft: FormatToken): Policy =
+ p1.unexpired(ft) | p2.unexpired(ft)
+
+ override def filter(pred: Clause => Boolean): Policy =
+ p1.filter(pred) | p2.filter(pred)
+
+ override def exists(pred: Clause => Boolean): Boolean =
+ p1.exists(pred) || p2.exists(pred)
+
+ override def noDequeue: Boolean =
+ p1.noDequeue || p2.noDequeue
+
+ override def toString: String = s"($p1 | $p2)"
+ }
+
+ private class AndThen(p1: Policy, p2: Policy) extends Policy {
+ override lazy val f: Pf = {
+ case x =>
+ p2.f.applyOrElse(
+ p1.f.andThen(x.withSplits _).applyOrElse(x, identity[Decision]),
+ (y: Decision) => y.splits
+ )
+ }
+
+ override def unexpired(ft: FormatToken): Policy =
+ p1.unexpired(ft) & p2.unexpired(ft)
+
+ override def filter(pred: Clause => Boolean): Policy =
+ p1.filter(pred) & p2.filter(pred)
+
+ override def exists(pred: Clause => Boolean): Boolean =
+ p1.exists(pred) || p2.exists(pred)
+
+ override def noDequeue: Boolean =
+ p1.noDequeue || p2.noDequeue
+
+ override def toString: String = s"($p1 & $p2)"
+ }
+
+ object Proxy {
+ def apply(
+ policy: Policy,
+ end: Option[End.WithPos] = None
+ )(factory: Policy => Pf)(implicit line: sourcecode.Line): Policy =
+ if (policy.isEmpty) NoPolicy
+ else new Proxy(policy, factory, end)
+ }
+
+ private class Proxy(
+ policy: Policy,
+ factory: Policy => Policy.Pf,
+ end: Option[End.WithPos] = None
+ )(implicit line: sourcecode.Line)
+ extends Policy {
+ override val f: Pf = factory(policy)
+
+ override def exists(pred: Clause => Boolean): Boolean = policy.exists(pred)
+
+ override def filter(pred: Clause => Boolean): Policy =
+ Proxy(policy.filter(pred))(factory)
+
+ override def unexpired(ft: FormatToken): Policy =
+ if (!end.forall(_.notExpiredBy(ft))) NoPolicy
+ else Proxy(policy.unexpired(ft), end)(factory)
+
+ override def noDequeue: Boolean = policy.noDequeue
+
+ override def toString: String = s"*($policy)${end.getOrElse("")}"
+ }
+
+ sealed trait End extends (Token => End.WithPos) {
+ def apply(endPos: Int): End.WithPos
+ final def apply(token: Token): End.WithPos = apply(token.end)
+ }
+ object End {
+ sealed trait WithPos {
+ def notExpiredBy(ft: FormatToken): Boolean
+ }
+ case object After extends End {
+ def apply(endPos: Int): WithPos =
+ new End.WithPos {
+ def notExpiredBy(ft: FormatToken): Boolean = ft.left.end <= endPos
+ override def toString: String = s">$endPos"
+ }
+ }
+ case object Before extends End {
+ def apply(endPos: Int): WithPos =
+ new End.WithPos {
+ def notExpiredBy(ft: FormatToken): Boolean = ft.right.end < endPos
+ override def toString: String = s"<$endPos"
+ }
+ }
+ case object On extends End {
+ def apply(endPos: Int): WithPos =
+ new End.WithPos {
+ def notExpiredBy(ft: FormatToken): Boolean = ft.right.end <= endPos
+ override def toString: String = s"@$endPos"
+ }
+ }
+ }
- def isEmpty(pf: Pf): Boolean = pf == emptyPf
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala
index e9bfeb1b4f..1204ac6fba 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/PolicySummary.scala
@@ -1,6 +1,5 @@
package org.scalafmt.internal
-import org.scalafmt.internal.Policy.NoPolicy
import org.scalafmt.util.LoggerOps
class PolicySummary(val policies: Vector[Policy]) {
@@ -8,12 +7,13 @@ class PolicySummary(val policies: Vector[Policy]) {
@inline def noDequeue = policies.exists(_.noDequeue)
- def combine(other: Policy, position: Int): PolicySummary = {
+ def combine(other: Policy, ft: FormatToken): PolicySummary = {
// TODO(olafur) filter policies by expiration date
- val activePolicies = policies.filter(_.expire > position)
+ val activePolicies = policies.flatMap(_.unexpiredOpt(ft))
+ val activeOther = other.unexpired(ft)
val newPolicies =
- if (other == NoPolicy) activePolicies
- else other +: activePolicies
+ if (activeOther.isEmpty) activePolicies
+ else activeOther +: activePolicies
new PolicySummary(newPolicies)
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala
index 1ba3092005..7508b2df87 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Router.scala
@@ -1,12 +1,7 @@
package org.scalafmt.internal
import org.scalafmt.Error.UnexpectedTree
-import org.scalafmt.config.{
- ImportSelectors,
- NewlineCurlyLambda,
- Newlines,
- ScalafmtConfig
-}
+import org.scalafmt.config.{ImportSelectors, Newlines, ScalafmtConfig}
import org.scalafmt.internal.ExpiresOn.{After, Before}
import org.scalafmt.internal.Length.{Num, StateColumn}
import org.scalafmt.internal.Policy.NoPolicy
@@ -61,6 +56,7 @@ class Router(formatOps: FormatOps) {
import Constants._
import LoggerOps._
+ import PolicyOps._
import TokenOps._
import TreeOps._
import formatOps._
@@ -72,9 +68,21 @@ class Router(formatOps: FormatOps) {
val newlines = formatToken.newlinesBetween
formatToken match {
- case FormatToken(_: T.BOF, _, _) =>
+ case FormatToken(_: T.BOF, right, _) =>
+ val policy = right match {
+ case T.Ident(name) // shebang in .sc files
+ if filename.endsWith(".sc") && name.startsWith("#!") =>
+ val nl = findFirst(next(formatToken), Int.MaxValue)(_.hasBreak)
+ nl.fold[Policy](Policy.NoPolicy) { ft =>
+ Policy.on(ft.left) {
+ case Decision(t, _) =>
+ Seq(Split(Space(t.between.nonEmpty), 0))
+ }
+ }
+ case _ => Policy.NoPolicy
+ }
Seq(
- Split(NoSplit, 0)
+ Split(NoSplit, 0).withPolicy(policy)
)
case FormatToken(_, _: T.EOF, _) =>
Seq(
@@ -83,8 +91,8 @@ class Router(formatOps: FormatOps) {
case FormatToken(start: T.Interpolation.Start, _, _) =>
val end = matching(start)
val policy =
- if (isTripleQuote(start)) NoPolicy
- else penalizeAllNewlines(end, BreakSingleLineInterpolatedString)
+ if (isTripleQuote(formatToken.meta.left.text)) NoPolicy
+ else PenalizeAllNewlines(end, BreakSingleLineInterpolatedString)
val split = Split(NoSplit, 0).withPolicy(policy)
Seq(
if (getStripMarginChar(formatToken).isEmpty) split
@@ -94,6 +102,7 @@ class Router(formatOps: FormatOps) {
.withIndent(StateColumn, end, After)
.withIndent(-1, end, After)
)
+ // Interpolation
case FormatToken(
_: T.Interpolation.Id | _: T.Interpolation.Part |
_: T.Interpolation.Start | _: T.Interpolation.SpliceStart,
@@ -130,11 +139,11 @@ class Router(formatOps: FormatOps) {
close,
disallowSingleLineComments = disallowSingleLineComments
)
- val newlineBeforeClosingCurly = newlinesOnlyBeforeClosePolicy(close)
+ val newlineBeforeClosingCurly = decideNewlinesOnlyBeforeClose(close)
val newlinePolicy = style.importSelectors match {
case ImportSelectors.noBinPack =>
- newlineBeforeClosingCurly.andThen(OneArgOneLineSplit(formatToken))
+ newlineBeforeClosingCurly & splitOneArgOneLine(close, leftOwner)
case ImportSelectors.binPack =>
newlineBeforeClosingCurly
case ImportSelectors.singleLine =>
@@ -165,52 +174,53 @@ class Router(formatOps: FormatOps) {
case tok @ FormatToken(open @ T.LeftBrace(), right, between) =>
val close = matching(open)
val closeFT = tokens(close)
- val newlineBeforeClosingCurly = newlinesOnlyBeforeClosePolicy(close)
+ val newlineBeforeClosingCurly = decideNewlinesOnlyBeforeClose(close)
val selfAnnotation: Option[Tokens] = leftOwner match {
// Self type: trait foo { self => ... }
case t: Template => Some(t.self.name.tokens).filter(_.nonEmpty)
case _ => None
}
- val isSelfAnnotation =
+ val isSelfAnnotationNL =
style.optIn.selfAnnotationNewline && selfAnnotation.nonEmpty && (
formatToken.hasBreak || style.newlines.sourceIgnored
)
val nl: Modification =
- if (isSelfAnnotation)
+ if (isSelfAnnotationNL)
getModCheckIndent(formatToken, math.max(newlines, 1))
else
- NewlineT(tok.hasBlankLine || blankLineBeforeDocstring(open, right))
+ NewlineT(tok.hasBlankLine || blankLineBeforeDocstring(tok))
- val (lambdaExpire, lambdaArrow, lambdaIndent) =
+ val (lambdaExpire, lambdaArrow, lambdaIndent, lambdaNLOnly) =
startsStatement(right) match {
case Some(owner: Term.Function) =>
val arrow = getFuncArrow(lastLambda(owner))
- val expire =
- arrow.getOrElse(tokens(owner.params.last.tokens.last))
- (expire, arrow.map(_.left), 0)
+ val expire = arrow.getOrElse(tokens(owner.tokens.last))
+ val nlOnly =
+ style.newlines.alwaysBeforeCurlyBraceLambdaParams
+ (expire, arrow.map(_.left), 0, nlOnly)
case _ =>
selfAnnotation match {
case Some(anno) =>
val arrow = leftOwner.tokens.find(_.is[T.RightArrow])
val expire = arrow.getOrElse(anno.last)
- (tokens(expire), arrow, 2)
+ (tokens(expire), arrow, 2, isSelfAnnotationNL)
case _ =>
- (null, None, 0)
+ (null, None, 0, true)
}
}
val lambdaPolicy =
if (lambdaExpire == null) null
else {
val arrowOptimal = getOptimalTokenFor(lambdaExpire)
- newlineBeforeClosingCurly
- .andThen(SingleLineBlock(arrowOptimal))
- .andThen(decideNewlinesOnlyAfterToken(arrowOptimal))
+ newlineBeforeClosingCurly &
+ SingleLineBlock(arrowOptimal) &
+ decideNewlinesOnlyAfterToken(arrowOptimal)
}
def getSingleLineDecisionPre2019NovOpt =
leftOwner.parent match {
case Some(_: Term.If | _: Term.Try | _: Term.TryWithHandler) => None
- case _ => Some(Policy.emptyPf)
+ case _ => Some(Policy.NoPolicy)
}
def getSingleLineDecisionFor2019Nov = {
type Classifiers = Seq[Classifier[Token, _]]
@@ -233,7 +243,7 @@ class Router(formatOps: FormatOps) {
val afterClose = closeFT.right
classifiers.exists(_(afterClose))
}
- if (!breakSingleLineAfterClose) Policy.emptyPf
+ if (!breakSingleLineAfterClose) Policy.NoPolicy
else decideNewlinesOnlyAfterClose(Split(Newline, 0))(close)
}
def getClassicSingleLineDecisionOpt =
@@ -244,7 +254,7 @@ class Router(formatOps: FormatOps) {
getSingleLineDecisionPre2019NovOpt
def getSingleLineLambdaDecisionOpt = {
- val ok = !style.newlines.alwaysBeforeCurlyBraceLambdaParams &&
+ val ok = !lambdaNLOnly &&
getSpaceAndNewlineAfterCurlyLambda(newlines)._1
if (ok) Some(getSingleLineDecisionFor2019Nov) else None
}
@@ -285,21 +295,16 @@ class Router(formatOps: FormatOps) {
singleLineDecisionOpt.fold(Split.ignored) { sld =>
val useOpt = lambdaPolicy != null || style.activeForEdition_2020_03
val expire = if (useOpt) endOfSingleLineBlock(closeFT) else close
- val policy =
- SingleLineBlock(expire, penaliseNewlinesInsideTokens = true)
- .andThen(sld)
- Split(xmlSpace(leftOwner), 0, policy = policy)
- .withOptimalToken(expire, killOnFail = true)
+ Split(xmlSpace(leftOwner), 0)
+ .withSingleLine(expire, noSyntaxNL = true, killOnFail = true)
+ .andPolicy(sld)
}
- Seq(
+ val splits = Seq(
singleLineSplit,
Split(Space, 0)
- .notIf(
- style.newlines.alwaysBeforeCurlyBraceLambdaParams ||
- isSelfAnnotation || lambdaPolicy == null ||
- style.newlines.sourceIs(Newlines.keep) && newlines != 0
- )
+ .notIf(lambdaNLOnly || lambdaPolicy == null)
+ .notIf(style.newlines.sourceIs(Newlines.keep) && newlines != 0)
.withOptimalTokenOpt(lambdaArrow)
.withIndent(lambdaIndent, close, Before)
.withPolicy(lambdaPolicy),
@@ -307,6 +312,11 @@ class Router(formatOps: FormatOps) {
.withPolicy(newlineBeforeClosingCurly)
.withIndent(2, close, Before)
)
+ right match {
+ case t: T.Xml.Start => withIndentOnXmlStart(t, splits)
+ case _ => splits
+ }
+
case FormatToken(arrow @ T.RightArrow(), right, _)
if startsStatement(right).isDefined &&
leftOwner.isInstanceOf[Term.Function] =>
@@ -323,13 +333,10 @@ class Router(formatOps: FormatOps) {
afterCurlySpace && style.activeForEdition_2020_01 &&
(!rightOwner.is[Defn] || style.newlines.sourceIs(Newlines.fold))
)
- Split(Space, 0)
- .withPolicy(
- SingleLineBlock(
- getOptimalTokenFor(endOfFunction),
- penaliseNewlinesInsideTokens = true
- )
- )
+ Split(Space, 0).withSingleLineNoOptimal(
+ getOptimalTokenFor(endOfFunction),
+ noSyntaxNL = true
+ )
else Split.ignored
Seq(
spaceSplit,
@@ -360,7 +367,8 @@ class Router(formatOps: FormatOps) {
leftOwner.is[Template] || leftOwner.parent.exists(_.is[Template])
def noSquash =
- style.newlines.afterCurlyLambda != NewlineCurlyLambda.squash
+ style.newlines.afterCurlyLambda ne
+ Newlines.AfterCurlyLambdaParams.squash
isCurlyLambda && (style.newlines.source match {
case Newlines.fold => false
@@ -372,7 +380,7 @@ class Router(formatOps: FormatOps) {
val singleLineSplit =
Split(Space, 0)
.notIf(hasSingleLineComment || noSingleLine)
- .withPolicy(SingleLineBlock(endOfFunction))
+ .withSingleLineNoOptimal(endOfFunction)
def newlineSplit =
Split(Newline, 1 + nestedApplies(leftOwner))
.withIndent(indent, endOfFunction, expiresOn)
@@ -405,22 +413,24 @@ class Router(formatOps: FormatOps) {
singleLineSplit +: multiLineSplits
// Case arrow
- case tok @ FormatToken(arrow @ T.RightArrow(), right, between)
- if leftOwner.isInstanceOf[Case] =>
+ case tok @ FormatToken(_: T.RightArrow, right, _) if leftOwner.is[Case] =>
val caseStat = leftOwner.asInstanceOf[Case]
if (right.is[T.LeftBrace] && (caseStat.body eq rightOwner))
// Redundant {} block around case statements.
Seq(Split(Space, 0).withIndent(-2, rightOwner.tokens.last, After))
else {
- def newlineSplit(cost: Int) =
- Split(NewlineT(noIndent = rhsIsCommentedOut(tok)), cost)
+ def newlineSplit(cost: Int) = {
+ val noIndent = rhsIsCommentedOut(tok)
+ val isDouble = tok.hasBlankLine && caseStat.body.tokens.isEmpty
+ Split(NewlineT(isDouble = isDouble, noIndent = noIndent), cost)
+ }
def foldedSplits =
caseStat.body match {
case _ if right.is[T.KwCase] || isSingleLineComment(right) =>
Right(Split.ignored)
case t if t.tokens.isEmpty || caseStat.cond.isDefined =>
Right(Split(Space, 0).withSingleLineOpt(t.tokens.lastOption))
- case t: Term.If if t.elsep.tokens.isEmpty =>
+ case t: Term.If if ifWithoutElse(t) =>
// must not use optimal token here, will lead to column overflow
Right(
Split(Space, 0).withSingleLineNoOptimal(t.cond.tokens.last)
@@ -434,7 +444,7 @@ class Router(formatOps: FormatOps) {
val exclude = insideBlockRanges[LeftParenOrBrace](tok, end)
val splits = Seq(
Split(Space, 0).withSingleLine(end, exclude),
- newlineSplit(1).withPolicy(penalizeAllNewlines(end, 1))
+ newlineSplit(1).withPolicy(PenalizeAllNewlines(end, 1))
)
Left(splits)
case t =>
@@ -551,14 +561,11 @@ class Router(formatOps: FormatOps) {
// https://github.com/scalameta/scalafmt/issues/1528
case init: Init if init.parent.forall(_.is[Mod.Annot]) => Space
case t: Term.Name
- if style.spaces.afterTripleEquals &&
- t.tokens.map(_.syntax) == Seq("===") =>
+ if style.spaces.afterTripleEquals && t.value == "===" =>
Space
case name: Term.Name
- if style.spaces.afterSymbolicDefs && isSymbolicName(
- name.value
- ) && name.parent
- .exists(isDefDef) =>
+ if style.spaces.afterSymbolicDefs &&
+ isSymbolicName(name.value) && name.parent.exists(isDefDef) =>
Space
case _ => NoSplit
}
@@ -566,19 +573,19 @@ class Router(formatOps: FormatOps) {
Split(modification, 0)
)
// Defn.{Object, Class, Trait}
- case tok @ FormatToken(T.KwObject() | T.KwClass() | T.KwTrait(), _, _) =>
+ case FormatToken(_: T.KwObject | _: T.KwClass | _: T.KwTrait, r, _) =>
val expire = defnTemplate(leftOwner)
.flatMap(templateCurly)
.getOrElse(leftOwner.tokens.last)
- val forceNewlineBeforeExtends = Policy(expire) {
- case d @ Decision(t @ FormatToken(_, _: T.KwExtends, _), _)
+ val forceNewlineBeforeExtends = Policy.before(expire) {
+ case Decision(t @ FormatToken(_, _: T.KwExtends, _), s)
if t.meta.rightOwner == leftOwner =>
- d.onlyNewlinesWithoutFallback
+ s.filter(x => x.isNL && !x.isActiveFor(SplitTag.OnelineWithChain))
}
- Seq(
- Split(Space, 0).withSingleLine(expire, killOnFail = true),
- Split(Space, 1).withPolicy(forceNewlineBeforeExtends)
- )
+ val policyExpire = defnBeforeTemplate(leftOwner).fold(r)(_.tokens.last)
+ val policyEnd = Policy.End.After(policyExpire)
+ val policy = delayedBreakPolicy(policyEnd)(forceNewlineBeforeExtends)
+ Seq(Split(Space, 0).withPolicy(policy))
// DefDef
case tok @ FormatToken(T.KwDef(), name @ T.Ident(_), _) =>
Seq(
@@ -614,13 +621,11 @@ class Router(formatOps: FormatOps) {
val close = matching(formatToken.left)
val newlinePolicy =
if (!style.danglingParentheses.callSite) None
- else Some(newlinesOnlyBeforeClosePolicy(close))
- val spacePolicy = SingleLineBlock(lambdaToken).orElse {
+ else Some(decideNewlinesOnlyBeforeClose(close))
+ val spacePolicy = SingleLineBlock(lambdaToken) | {
if (lambdaIsABlock) None
else
- newlinePolicy.map(
- delayedBreakPolicy(lambdaLeft.map(open => _.end < open.end))
- )
+ newlinePolicy.map(delayedBreakPolicy(lambdaLeft.map(Policy.End.On)))
}
val noSplitMod = getNoSplit(formatToken, true)
@@ -638,145 +643,9 @@ class Router(formatOps: FormatOps) {
)
}
- case FormatToken(LeftParenOrBracket(), right, between)
- if style.optIn.configStyleArguments && isDefnOrCallSite(leftOwner) &&
- (opensConfigStyle(formatToken, false) || {
- forceConfigStyle(leftOwner) && !styleMap.forcedBinPack(leftOwner)
- }) =>
- val open = formatToken.left
- val indent = getApplyIndent(leftOwner, isConfigStyle = true)
- val close = matching(open)
- val newlineBeforeClose = newlinesOnlyBeforeClosePolicy(close)
- val extraIndent: Length =
- if (style.poorMansTrailingCommasInConfigStyle) Num(2)
- else Num(0)
- val isForcedBinPack = styleMap.forcedBinPack.contains(leftOwner)
- val policy =
- if (isForcedBinPack) newlineBeforeClose
- else OneArgOneLineSplit(formatToken).orElse(newlineBeforeClose)
- val implicitSplit =
- if (opensConfigStyleImplicitParamList(formatToken))
- Split(Space(style.spaces.inParentheses), 0)
- .withPolicy(policy.orElse(decideNewlinesOnlyAfterToken(right)))
- .withOptimalToken(right, killOnFail = true)
- .withIndent(indent, close, Before)
- .withIndent(extraIndent, right, Before)
- else Split.ignored
- Seq(
- implicitSplit,
- Split(Newline, if (implicitSplit.isActive) 1 else 0, policy = policy)
- .withIndent(indent, close, Before)
- .withIndent(extraIndent, right, After)
- )
-
- case FormatToken(open @ LeftParenOrBracket(), right, between)
- if style.binPack.unsafeDefnSite && isDefnSite(leftOwner) =>
- val close = matching(open)
- val isBracket = open.is[T.LeftBracket]
- val indent = Num(style.continuationIndent.getDefnSite(leftOwner))
- if (isTuple(leftOwner)) {
- Seq(
- Split(NoSplit, 0).withPolicy(
- SingleLineBlock(close, disallowSingleLineComments = false)
- )
- )
- } else {
- def penalizeBrackets(penalty: Int): Policy =
- if (isBracket)
- penalizeAllNewlines(close, Constants.BracketPenalty * penalty + 3)
- else NoPolicy
- val bracketCoef = if (isBracket) Constants.BracketPenalty else 1
- val bracketPenalty = if (isBracket) 1 else 0
- val nestingPenalty = nestedApplies(leftOwner)
-
- val noSplitPenalizeNewlines = penalizeBrackets(1 + bracketPenalty)
- val mustDangle = style.newlines.sourceIgnored &&
- style.danglingParentheses.defnSite
- val noSplitPolicy: Policy =
- if (mustDangle) SingleLineBlock(close)
- else
- argumentStarts.get(hash(right)) match {
- case Some(arg) =>
- val singleLine = SingleLineBlock(arg.tokens.last)
- if (isBracket) {
- noSplitPenalizeNewlines.andThen(singleLine.f)
- } else {
- singleLine
- }
- case _ => noSplitPenalizeNewlines
- }
- val noSplitModification =
- if (right.is[T.Comment]) getMod(formatToken)
- else NoSplit
- val nlDanglePolicy =
- if (mustDangle) newlinesOnlyBeforeClosePolicy(close) else NoPolicy
-
- Seq(
- Split(noSplitModification, 0 + (nestingPenalty * bracketCoef))
- .withPolicy(noSplitPolicy)
- .withIndent(indent, close, Before),
- Split(Newline, (1 + nestingPenalty * nestingPenalty) * bracketCoef)
- .notIf(right.is[T.RightParen])
- .withPolicy(penalizeBrackets(1))
- .andThenPolicy(nlDanglePolicy)
- .withIndent(indent, close, Before)
- )
- }
- case FormatToken(LeftParenOrBracket(), _, _)
- if style.binPack.unsafeCallSite && isCallSite(leftOwner) =>
- val open = formatToken.left
- val close = matching(open)
- val indent = getApplyIndent(leftOwner)
- def baseNoSplit = Split(NoSplit, 0).withIndent(indent, close, Before)
- val singleLineOnly = style.binPack.literalsSingleLine &&
- styleMap.opensLiteralArgumentList(formatToken)
-
- val noSplit =
- if (singleLineOnly || style.newlines.sourceIgnored)
- baseNoSplit.withSingleLine(close)
- else {
- val opt = leftOwner.tokens.find(_.is[T.Comma]).orElse(Some(close))
- val isBracket = open.is[T.LeftBracket]
- // TODO(olafur) DRY. Same logic as in default.
- val exclude =
- if (isBracket)
- insideBlock[T.LeftBracket](formatToken, close)
- else
- insideBlock[T.LeftBrace](formatToken, close)
- val excludeRanges: Set[Range] =
- exclude.map((matchingParensRange _).tupled).toSet
- val unindent = UnindentAtExclude(exclude.keySet, Num(-indent.n))
- def ignoreBlocks(x: FormatToken): Boolean = {
- excludeRanges.exists(_.contains(x.left.end))
- }
- val policy =
- penalizeAllNewlines(close, 3, ignore = ignoreBlocks)
- .andThen(unindent)
- baseNoSplit.withOptimalTokenOpt(opt).withPolicy(policy)
- }
-
- val nlDanglePolicy =
- if (
- style.newlines.sourceIgnored &&
- style.danglingParentheses.callSite
- )
- newlinesOnlyBeforeClosePolicy(close)
- else NoPolicy
- val nlIndent = if (style.activeForEdition_2020_03) indent else Num(4)
- Seq(
- noSplit,
- Split(NewlineT(acceptNoSplit = singleLineOnly), 2)
- .withIndent(nlIndent, close, Before)
- .withSingleLineOpt(if (singleLineOnly) Some(close) else None)
- .andThenPolicy(nlDanglePolicy)
- )
case FormatToken(T.LeftParen(), T.RightParen(), _) =>
- Seq(Split(NoSplit, 0))
-
- // If configured to skip the trailing space after `if` and other keywords, do so.
- case FormatToken(T.KwIf() | T.KwFor() | T.KwWhile(), T.LeftParen(), _)
- if !style.spaces.afterKeywordBeforeParen =>
- Seq(Split(NoSplit, 0))
+ val noNL = style.newlines.sourceIgnored || formatToken.noBreak
+ Seq(Split(NoSplit.orNL(noNL), 0))
case tok @ FormatToken(open @ LeftParenOrBracket(), right, _)
if !isSuperfluousParenthesis(formatToken.left, leftOwner) &&
@@ -796,11 +665,13 @@ class Router(formatOps: FormatOps) {
val isBracket = open.is[T.LeftBracket]
val bracketCoef = if (isBracket) Constants.BracketPenalty else 1
+ val onlyConfigStyle = mustUseConfigStyle(formatToken)
+
val sourceIgnored = style.newlines.sourceIgnored
- val notSingleEnclosedArgument =
- sourceIgnored && !(singleArgument && isEnclosedInMatching(args(0)))
- val useConfigStyle =
- style.optIn.configStyleArguments && notSingleEnclosedArgument
+ val isSingleEnclosedArgument =
+ singleArgument && isEnclosedInMatching(args(0))
+ val useConfigStyle = onlyConfigStyle || (sourceIgnored &&
+ style.optIn.configStyleArguments && !isSingleEnclosedArgument)
def isExcludedTree(tree: Tree): Boolean =
tree match {
@@ -813,24 +684,25 @@ class Router(formatOps: FormatOps) {
case _ => false
}
- val nestedPenalty = nestedApplies(leftOwner) + lhsPenalty
+ val nestedPenalty = 1 + nestedApplies(leftOwner) + lhsPenalty
val excludeRanges =
if (isBracket) insideBlockRanges[T.LeftBracket](tok, close)
else if (
style.activeForEdition_2020_03 && multipleArgs ||
- notSingleEnclosedArgument &&
+ !isSingleEnclosedArgument &&
style.newlines.sourceIs(Newlines.unfold)
)
Set.empty[Range]
else if (
- style.newlines.sourceIs(Newlines.fold) &&
- singleArgument &&
- (!notSingleEnclosedArgument || isExcludedTree(args(0)))
+ style.newlines.sourceIs(Newlines.fold) && {
+ isSingleEnclosedArgument ||
+ singleArgument && isExcludedTree(args(0))
+ }
)
parensRange(args(0).tokens.last).toSet
else insideBlockRanges[T.LeftBrace](tok, close)
- val indent = getApplyIndent(leftOwner)
+ val indent = getApplyIndent(leftOwner, onlyConfigStyle)
def insideBraces(t: FormatToken): Boolean =
excludeRanges.exists(_.contains(t.left.start))
@@ -839,8 +711,8 @@ class Router(formatOps: FormatOps) {
newlinePenalty: Int
)(implicit line: sourcecode.Line): Policy = {
val baseSingleLinePolicy = if (isBracket) {
- if (singleArgument)
- penalizeAllNewlines(
+ if (!multipleArgs)
+ PenalizeAllNewlines(
close,
newlinePenalty,
penalizeLambdas = false
@@ -848,22 +720,20 @@ class Router(formatOps: FormatOps) {
else SingleLineBlock(close)
} else {
val penalty =
- if (singleArgument) newlinePenalty
+ if (!multipleArgs) newlinePenalty
else Constants.ShouldBeNewline
- penalizeAllNewlines(
+ PenalizeAllNewlines(
close,
penalty = penalty,
ignore = insideBraces,
- penalizeLambdas = !singleArgument,
- penaliseNewlinesInsideTokens = !singleArgument
+ penalizeLambdas = multipleArgs,
+ penaliseNewlinesInsideTokens = multipleArgs
)
}
baseSingleLinePolicy
}
- val newlineMod: Modification = NoSplit.orNL(right.is[T.LeftBrace])
-
val defnSite = isDefnSite(leftOwner)
val closeFormatToken = tokens(close)
val expirationToken: Token =
@@ -872,7 +742,7 @@ class Router(formatOps: FormatOps) {
else
rhsOptimalToken(closeFormatToken)
- val mustDangle = style.activeForEdition_2020_01 && (
+ val mustDangle = onlyConfigStyle || style.activeForEdition_2020_01 && (
expirationToken.is[T.Comment]
)
val shouldDangle =
@@ -885,18 +755,23 @@ class Router(formatOps: FormatOps) {
val newlinePolicy: Policy =
if (wouldDangle || mustDangle) {
- newlinesOnlyBeforeClosePolicy(close)
+ decideNewlinesOnlyBeforeClose(close)
} else {
- Policy.empty(close)
+ Policy.NoPolicy
}
- val handleImplicit = style.activeForEdition_2020_03 &&
- opensImplicitParamList(formatToken, args)
+ val handleImplicit =
+ if (onlyConfigStyle) opensConfigStyleImplicitParamList(formatToken)
+ else
+ style.activeForEdition_2020_03 &&
+ opensImplicitParamList(formatToken, args)
val noSplitMod =
if (
- handleImplicit &&
- style.newlines.forceBeforeImplicitParamListModifier
+ style.newlines.sourceIs(Newlines.keep) && tok.hasBreak || {
+ if (!handleImplicit) onlyConfigStyle
+ else style.newlines.forceBeforeImplicitParamListModifier
+ }
)
null
else getNoSplit(formatToken, !isBracket)
@@ -907,7 +782,7 @@ class Router(formatOps: FormatOps) {
else style.align.openParenCallSite
} && (!handleImplicit ||
style.newlines.forceAfterImplicitParamListModifier)
- val alignTuple = align && isTuple(leftOwner)
+ val alignTuple = align && isTuple(leftOwner) && !onlyConfigStyle
val keepConfigStyleSplit = !sourceIgnored &&
style.optIn.configStyleArguments && newlines != 0
@@ -921,57 +796,211 @@ class Router(formatOps: FormatOps) {
}
val breakToken = getOptimalTokenFor(assignToken)
val newlineAfterAssignDecision =
- if (newlinePolicy.isEmpty) Policy.emptyPf
+ if (newlinePolicy.isEmpty) Policy.NoPolicy
else decideNewlinesOnlyAfterToken(breakToken)
- val noSplitCost = 1 + nestedPenalty
- val newlineCost = Constants.ExceedColumnPenalty + noSplitCost
Seq(
- Split(Newline, newlineCost)
+ Split(Newline, nestedPenalty + Constants.ExceedColumnPenalty)
.withPolicy(newlinePolicy)
.withIndent(indent, close, Before),
- Split(NoSplit, noSplitCost)
+ Split(NoSplit, nestedPenalty)
.withSingleLine(breakToken)
- .andThenPolicy(
- newlinePolicy.andThen(newlineAfterAssignDecision)
- )
+ .andPolicy(newlinePolicy & newlineAfterAssignDecision)
)
}
- val noSplitPolicy =
- if (wouldDangle || mustDangle && isBracket || useConfigStyle)
- SingleLineBlock(close, exclude = excludeRanges)
- else if (splitsForAssign.isDefined)
- singleLine(3)
- else
- singleLine(10)
- val oneArgOneLine =
- newlinePolicy.andThen(OneArgOneLineSplit(formatToken))
+ val preferNoSplit = singleArgument &&
+ style.newlines.sourceIs(Newlines.keep) && tok.noBreak
+ val oneArgOneLine = newlinePolicy & splitOneArgOneLine(close, leftOwner)
+ val extraOneArgPerLineIndent =
+ if (multipleArgs && style.poorMansTrailingCommasInConfigStyle)
+ Indent(Num(2), right, After)
+ else Indent.Empty
val (implicitPenalty, implicitPolicy) =
- if (!handleImplicit) (2, Policy.emptyPf)
+ if (!handleImplicit) (2, Policy.NoPolicy)
else (0, decideNewlinesOnlyAfterToken(right))
- Seq(
- Split(noSplitMod, 0, policy = noSplitPolicy)
- .onlyIf(noSplitMod != null)
- .withOptimalToken(expirationToken)
- .withIndent(noSplitIndent, close, Before),
- Split(newlineMod, (1 + nestedPenalty) * bracketCoef)
- .withPolicy(newlinePolicy.andThen(singleLine(4)))
- .onlyIf(!multipleArgs && !alignTuple && splitsForAssign.isEmpty)
- .withOptimalToken(expirationToken)
- .withIndent(indent, close, Before),
- Split(noSplitMod, (implicitPenalty + lhsPenalty) * bracketCoef)
- .withPolicy(oneArgOneLine.andThen(implicitPolicy))
- .onlyIf(noSplitMod != null)
- .onlyIf(
- (notTooManyArgs && align) || (handleImplicit &&
- style.newlines.notBeforeImplicitParamListModifier)
+
+ val splitsNoNL =
+ if (noSplitMod == null) Seq.empty
+ else if (onlyConfigStyle)
+ Seq(
+ Split(noSplitMod, 0)
+ .withPolicy(oneArgOneLine & implicitPolicy)
+ .withOptimalToken(right, killOnFail = true)
+ .withIndent(extraOneArgPerLineIndent)
+ .withIndent(indent, close, Before)
+ )
+ else {
+ val noSplitPolicy =
+ if (preferNoSplit) singleLine(2)
+ else if (wouldDangle || mustDangle && isBracket || useConfigStyle)
+ SingleLineBlock(close, exclude = excludeRanges)
+ else if (splitsForAssign.isDefined)
+ singleLine(3)
+ else
+ singleLine(10)
+ Seq(
+ Split(noSplitMod, 0, policy = noSplitPolicy)
+ .withOptimalToken(expirationToken)
+ .withIndent(noSplitIndent, close, Before),
+ Split(noSplitMod, (implicitPenalty + lhsPenalty) * bracketCoef)
+ .withPolicy(oneArgOneLine & implicitPolicy)
+ .onlyIf(
+ (notTooManyArgs && align) || (handleImplicit &&
+ style.newlines.notBeforeImplicitParamListModifier)
+ )
+ .withIndent(if (align) StateColumn else indent, close, Before)
+ )
+ }
+
+ val splitsNL =
+ if (
+ alignTuple || !(onlyConfigStyle ||
+ multipleArgs || splitsForAssign.isEmpty)
+ )
+ Seq.empty
+ else {
+ val cost =
+ if (onlyConfigStyle)
+ if (splitsNoNL.isEmpty) 0 else 1
+ else
+ (if (preferNoSplit) Constants.ExceedColumnPenalty else 0) +
+ bracketCoef * (nestedPenalty + (if (multipleArgs) 2 else 0))
+ val split =
+ if (multipleArgs)
+ Split(Newline, cost, policy = oneArgOneLine)
+ .withIndent(extraOneArgPerLineIndent)
+ else {
+ val noSplit = !onlyConfigStyle && right.is[T.LeftBrace]
+ val noConfigStyle = noSplit ||
+ newlinePolicy.isEmpty || !style.optIn.configStyleArguments
+ Split(NoSplit.orNL(noSplit), cost, policy = newlinePolicy)
+ .andPolicy(singleLine(4), !noConfigStyle)
+ }
+ Seq(split.withIndent(indent, close, Before))
+ }
+
+ splitsNoNL ++ splitsNL ++ splitsForAssign.getOrElse(Seq.empty)
+
+ case FormatToken(open @ LeftParenOrBracket(), right, between)
+ if style.binPack.unsafeDefnSite && isDefnSite(leftOwner) =>
+ val close = matching(open)
+ val isBracket = open.is[T.LeftBracket]
+ val indent = Num(style.continuationIndent.getDefnSite(leftOwner))
+ if (isTuple(leftOwner)) {
+ Seq(
+ Split(NoSplit, 0).withPolicy(
+ SingleLineBlock(close, disallowSingleLineComments = false)
)
- .withIndent(if (align) StateColumn else indent, close, Before),
- Split(Newline, (3 + nestedPenalty) * bracketCoef)
- .withPolicy(oneArgOneLine)
- .onlyIf(!singleArgument && !alignTuple)
- .withIndent(indent, close, Before)
- ) ++ splitsForAssign.getOrElse(Seq.empty)
+ )
+ } else {
+ def penalizeBrackets(penalty: Int): Policy =
+ if (isBracket)
+ PenalizeAllNewlines(close, Constants.BracketPenalty * penalty + 3)
+ else NoPolicy
+ val bracketCoef = if (isBracket) Constants.BracketPenalty else 1
+ val bracketPenalty = if (isBracket) 1 else 0
+ val nestingPenalty = nestedApplies(leftOwner)
+ val onlyConfigStyle = mustUseConfigStyle(formatToken)
+
+ val mustDangle = onlyConfigStyle ||
+ style.newlines.sourceIgnored && style.danglingParentheses.defnSite
+ val noSplitPolicy: Policy =
+ if (mustDangle) SingleLineBlock(close)
+ else {
+ val noSplitPenalizeNewlines = penalizeBrackets(1 + bracketPenalty)
+ argumentStarts.get(hash(right)) match {
+ case Some(arg) =>
+ val singleLine = SingleLineBlock(arg.tokens.last)
+ if (isBracket) {
+ noSplitPenalizeNewlines & singleLine
+ } else {
+ singleLine
+ }
+ case _ => noSplitPenalizeNewlines
+ }
+ }
+ val noSplitModification =
+ if (right.is[T.Comment]) getMod(formatToken)
+ else NoSplit
+ val nlDanglePolicy =
+ if (mustDangle) decideNewlinesOnlyBeforeClose(close) else NoPolicy
+
+ Seq(
+ Split(noSplitModification, 0 + (nestingPenalty * bracketCoef))
+ .notIf(onlyConfigStyle)
+ .withPolicy(noSplitPolicy)
+ .withIndent(indent, close, Before),
+ Split(Newline, (1 + nestingPenalty * nestingPenalty) * bracketCoef)
+ .notIf(right.is[T.RightParen])
+ .withPolicy(penalizeBrackets(1))
+ .andPolicy(nlDanglePolicy)
+ .withIndent(indent, close, Before)
+ )
+ }
+
+ case FormatToken(LeftParenOrBracket(), _, _)
+ if style.binPack.unsafeCallSite && isCallSite(leftOwner) =>
+ val open = formatToken.left
+ val close = matching(open)
+ val indent = getApplyIndent(leftOwner)
+ def baseNoSplit = Split(NoSplit, 0).withIndent(indent, close, Before)
+ val opensLiteralArgumentList =
+ styleMap.opensLiteralArgumentList(formatToken)
+ val singleLineOnly =
+ style.binPack.literalsSingleLine && opensLiteralArgumentList
+ val onlyConfigStyle =
+ mustUseConfigStyle(formatToken, !opensLiteralArgumentList)
+
+ val noSplit =
+ if (singleLineOnly || style.newlines.sourceIgnored)
+ baseNoSplit.withSingleLine(close)
+ else if (onlyConfigStyle) Split.ignored
+ else {
+ val opt = leftOwner.tokens.find(_.is[T.Comma]).orElse(Some(close))
+ val isBracket = open.is[T.LeftBracket]
+ // TODO(olafur) DRY. Same logic as in default.
+ val exclude =
+ if (isBracket)
+ insideBlock[T.LeftBracket](formatToken, close)
+ else
+ insideBlock[T.LeftBrace](formatToken, close)
+ val excludeRanges: Set[Range] =
+ exclude.map((matchingParensRange _).tupled).toSet
+ def ignoreBlocks(x: FormatToken): Boolean = {
+ excludeRanges.exists(_.contains(x.left.end))
+ }
+ val policy =
+ PenalizeAllNewlines(close, 3, ignore = ignoreBlocks) &
+ Policy.on(close) {
+ UnindentAtExclude(exclude.keySet, Num(-indent.n))
+ }
+ baseNoSplit.withOptimalTokenOpt(opt).withPolicy(policy)
+ }
+
+ def newlineBeforeClose = decideNewlinesOnlyBeforeClose(close)
+ val nlPolicy =
+ if (onlyConfigStyle) {
+ if (styleMap.forcedBinPack(leftOwner)) newlineBeforeClose
+ else splitOneArgOneLine(close, leftOwner) | newlineBeforeClose
+ } else if (
+ style.newlines.sourceIgnored &&
+ style.danglingParentheses.callSite
+ )
+ newlineBeforeClose
+ else NoPolicy
+ val nlIndent = if (style.activeForEdition_2020_03) indent else Num(4)
+ Seq(
+ noSplit,
+ Split(NewlineT(alt = if (singleLineOnly) Some(NoSplit) else None), 2)
+ .withIndent(nlIndent, close, Before)
+ .withSingleLineOpt(if (singleLineOnly) Some(close) else None)
+ .andPolicy(nlPolicy)
+ )
+
+ // If configured to skip the trailing space after `if` and other keywords, do so.
+ case FormatToken(T.KwIf() | T.KwFor() | T.KwWhile(), T.LeftParen(), _)
+ if !style.spaces.afterKeywordBeforeParen =>
+ Seq(Split(NoSplit, 0))
// Closing def site ): ReturnType
case FormatToken(left, T.Colon(), _)
@@ -979,7 +1008,7 @@ class Router(formatOps: FormatOps) {
defDefReturnType(leftOwner).isDefined =>
val expire = lastToken(defDefReturnType(rightOwner).get)
val penalizeNewlines =
- penalizeAllNewlines(expire, Constants.BracketPenalty)
+ PenalizeAllNewlines(expire, Constants.BracketPenalty)
val sameLineSplit = Space(endsWithSymbolIdent(left))
val indent = style.continuationIndent.getDefnSite(leftOwner)
Seq(
@@ -1006,9 +1035,11 @@ class Router(formatOps: FormatOps) {
)
case FormatToken(_, T.LeftBrace(), _) if isXmlBrace(rightOwner) =>
- Seq(
- Split(NoSplit, 0)
+ withIndentOnXmlSpliceStart(
+ formatToken,
+ Seq(Split(NoSplit, 0))
)
+
case FormatToken(T.RightBrace(), _, _) if isXmlBrace(leftOwner) =>
Seq(
Split(NoSplit, 0)
@@ -1033,8 +1064,8 @@ class Router(formatOps: FormatOps) {
val breakAfter =
rhsOptimalToken(next(nextNonCommentSameLine(formatToken)))
val multiLine =
- newlinesOnlyBeforeClosePolicy(close)
- .orElse(decideNewlinesOnlyAfterToken(breakAfter))
+ decideNewlinesOnlyBeforeClose(close) |
+ decideNewlinesOnlyAfterToken(breakAfter)
Seq(
Split(Newline, 0).withSingleLine(close, killOnFail = true),
Split(Space, 1, policy = multiLine)
@@ -1059,11 +1090,19 @@ class Router(formatOps: FormatOps) {
Split(NoSplit, 0)
)
// These are mostly filtered out/modified by policies.
+ case tok @ FormatToken(_: T.Comma, c: T.Comment, _) =>
+ if (isSingleLineComment(c)) Seq(Split(Space.orNL(tok.noBreak), 0))
+ else if (tok.meta.right.firstNL >= 0) Seq(Split(Newline, 0))
+ else {
+ val noNewline = newlines == 0 && style.activeForEdition_2020_01 &&
+ // perhaps left is a trailing comma
+ nextNonComment(next(tok)).right.is[RightParenOrBracket]
+ Seq(Split(Space, 0), Split(Newline, 1).notIf(noNewline))
+ }
case tok @ FormatToken(T.Comma(), right, _) =>
// TODO(olafur) DRY, see OneArgOneLine.
- val binPack = isBinPack(leftOwner)
argumentStarts.get(hash(right)) match {
- case Some(nextArg) if binPack =>
+ case Some(nextArg) if isBinPack(leftOwner) =>
val lastFT = tokens(nextArg.tokens.last)
Seq(
Split(Space, 0).withSingleLine(rhsOptimalToken(lastFT)),
@@ -1083,19 +1122,9 @@ class Router(formatOps: FormatOps) {
case _ =>
0
}
- val singleLineComment = isSingleLineComment(right)
- val noNewline = newlines == 0 && {
- singleLineComment || style.activeForEdition_2020_01 && {
- val nextTok = nextNonComment(tok).right
- // perhaps a trailing comma
- (nextTok ne right) && nextTok.is[RightParenOrBracket]
- }
- }
Seq(
- Split(Space, 0).notIf(newlines != 0 && singleLineComment),
- Split(Newline, 1)
- .notIf(noNewline)
- .withIndent(indent, right, After)
+ Split(Space, 0),
+ Split(Newline, 1).withIndent(indent, right, After)
)
}
case FormatToken(_, T.Semicolon(), _) =>
@@ -1165,7 +1194,7 @@ class Router(formatOps: FormatOps) {
}(getInfixSplitsBeforeLhs(_, formatToken, Right(rhs)))
case FormatToken(_, _: T.Dot, _)
- if style.newlines.sourceIgnored &&
+ if !style.newlines.sourceIs(Newlines.keep) &&
rightOwner.is[Term.Select] && findTreeWithParent(rightOwner) {
case _: Type.Select | _: Importer | _: Pkg => Some(true)
case _: Term.Select | SplitCallIntoParts(_, _) => None
@@ -1173,14 +1202,117 @@ class Router(formatOps: FormatOps) {
}.isDefined =>
Seq(Split(NoSplit, 0))
- case t @ FormatToken(left, _: T.Dot, _)
- if !style.newlines.sourceIs(Newlines.classic) &&
- rightOwner.is[Term.Select] =>
- val (expireTree, nextSelect) = findLastApplyAndNextSelect(rightOwner)
- val prevSelect = findPrevSelect(rightOwner.asInstanceOf[Term.Select])
+ case t @ FormatToken(left, _: T.Dot, _) if rightOwner.is[Term.Select] =>
+ val enclosed = style.encloseSelectChains
+ val (expireTree, nextSelect) =
+ findLastApplyAndNextSelect(rightOwner, enclosed)
+ val thisSelect = rightOwner.asInstanceOf[Term.Select]
+ val prevSelect = findPrevSelect(thisSelect, enclosed)
val expire = lastToken(expireTree)
+ def breakOnNextDot: Policy =
+ nextSelect.fold[Policy](Policy.NoPolicy) { tree =>
+ val end = tree.name.tokens.head
+ Policy.before(end) {
+ case Decision(t @ FormatToken(_, _: T.Dot, _), s)
+ if t.meta.rightOwner eq tree =>
+ val filtered = s.flatMap { x =>
+ val y = x.activateFor(SplitTag.SelectChainSecondNL)
+ if (y.isActive) Some(y) else None
+ }
+ if (filtered.isEmpty) Seq.empty
+ else {
+ val minCost = math.max(0, filtered.map(_.cost).min - 1)
+ filtered.map { x =>
+ val p =
+ x.policy.filter(!_.isInstanceOf[PenalizeAllNewlines])
+ x.copy(cost = x.cost - minCost, policy = p)
+ }
+ }
+ }
+ }
val baseSplits = style.newlines.source match {
+ case Newlines.classic =>
+ def getNlMod = {
+ val endSelect = nextSelect.fold(expire)(x => lastToken(x.qual))
+ val nlAlt = ModExt(NoSplit).withIndent(-2, endSelect, After)
+ NewlineT(alt = Some(nlAlt))
+ }
+
+ val prevChain = inSelectChain(prevSelect, thisSelect, expireTree)
+ if (canStartSelectChain(thisSelect, nextSelect, expireTree)) {
+ val chainExpire =
+ if (nextSelect.isEmpty) lastToken(thisSelect)
+ else if (!isEnclosedInMatching(expireTree)) expire
+ else lastToken(expireTree.tokens.dropRight(1))
+ val nestedPenalty =
+ nestedSelect(rightOwner) + nestedApplies(leftOwner)
+ // This policy will apply to both the space and newline splits, otherwise
+ // the newline is too cheap even it doesn't actually prevent other newlines.
+ val penalizeBreaks = PenalizeAllNewlines(chainExpire, 2)
+ def slbPolicy =
+ SingleLineBlock(
+ chainExpire,
+ getExcludeIf(chainExpire),
+ penaliseNewlinesInsideTokens = true
+ )
+ val newlinePolicy = breakOnNextDot & penalizeBreaks
+ val ignoreNoSplit = t.hasBreak &&
+ (left.is[T.Comment] || style.optIn.breakChainOnFirstMethodDot)
+ val chainLengthPenalty =
+ if (
+ style.newlines.penalizeSingleSelectMultiArgList &&
+ nextSelect.isEmpty
+ ) {
+ // penalize by the number of arguments in the rhs open apply.
+ // I know, it's a bit arbitrary, but my manual experiments seem
+ // to show that it produces OK output. The key insight is that
+ // many arguments on the same line can be hard to read. By not
+ // putting a newline before the dot, we force the argument list
+ // to break into multiple lines.
+ splitCallIntoParts.lift(tokens(t, 2).meta.rightOwner) match {
+ case Some((_, Left(args))) =>
+ Math.max(0, args.length - 1)
+ case Some((_, Right(argss))) =>
+ Math.max(0, argss.map(_.length).sum - 1)
+ case _ => 0
+ }
+ } else 0
+ // when the flag is on, penalize break, to avoid idempotence issues;
+ // otherwise, after the break is chosen, the flag prohibits nosplit
+ val nlBaseCost =
+ if (style.optIn.breakChainOnFirstMethodDot && t.noBreak) 3
+ else 2
+ val nlCost = nlBaseCost + nestedPenalty + chainLengthPenalty
+ val nlMod = getNlMod
+ Seq(
+ Split(!prevChain, 1) { // must come first, for backwards compat
+ if (style.optIn.breaksInsideChains) NoSplit.orNL(t.noBreak)
+ else nlMod
+ }
+ .withPolicy(newlinePolicy)
+ .onlyFor(SplitTag.SelectChainSecondNL),
+ Split(ignoreNoSplit, 0)(NoSplit)
+ .withPolicy(slbPolicy, prevChain)
+ .andPolicy(penalizeBreaks),
+ Split(if (ignoreNoSplit) Newline else nlMod, nlCost)
+ .withPolicy(newlinePolicy)
+ )
+ } else {
+ val isComment = left.is[T.Comment]
+ val doBreak = isComment && t.hasBreak
+ Seq(
+ Split(!prevChain, 1) {
+ if (style.optIn.breaksInsideChains) NoSplit.orNL(t.noBreak)
+ else if (doBreak) Newline
+ else getNlMod
+ }
+ .withPolicy(breakOnNextDot)
+ .onlyFor(SplitTag.SelectChainSecondNL),
+ Split(if (doBreak) Newline else Space(isComment), 0)
+ )
+ }
+
case _ if left.is[T.Comment] =>
Seq(Split(Space.orNL(t.noBreak), 0))
@@ -1192,15 +1324,15 @@ class Router(formatOps: FormatOps) {
Seq(Split(NoSplit, 0), Split(Newline, 1))
else {
val forcedBreakPolicy = nextSelect.map { tree =>
- Policy(tree.name.tokens.head) {
+ Policy.before(tree.name.tokens.head) {
case Decision(t @ FormatToken(_, _: T.Dot, _), s)
if t.meta.rightOwner eq tree =>
- s.filter(_.modification.isNewline)
+ s.filter(_.isNL)
}
}
Seq(
- Split(NoSplit, 0).withSingleLine(expire),
- Split(NewlineT(acceptNoSplit = true), 1)
+ Split(NoSplit, 0).withSingleLine(expire, noSyntaxNL = true),
+ Split(NewlineT(alt = Some(NoSplit)), 1)
.withPolicyOpt(forcedBreakPolicy)
)
}
@@ -1210,12 +1342,12 @@ class Router(formatOps: FormatOps) {
def exclude = insideBlockRanges[LeftParenOrBrace](t, end)
Seq(
Split(NoSplit, 0).withSingleLine(end, exclude),
- Split(NewlineT(acceptNoSplit = true), 1)
+ Split(NewlineT(alt = Some(NoSplit)), 1)
)
}
- val delayedBreakPolicy = nextSelect.map { tree =>
- Policy(tree.name.tokens.head) {
+ val delayedBreakPolicyOpt = nextSelect.map { tree =>
+ Policy.before(tree.name.tokens.head) {
case Decision(t @ FormatToken(_, _: T.Dot, _), s)
if t.meta.rightOwner eq tree =>
SplitTag.SelectChainFirstNL.activateOnly(s)
@@ -1224,73 +1356,15 @@ class Router(formatOps: FormatOps) {
// trigger indent only on the first newline
val indent = Indent(Num(2), expire, After)
- val willBreak =
- nextNonCommentSameLine(tokens(formatToken, 2)).right.is[T.Comment]
+ val willBreak = nextNonCommentSameLine(tokens(t, 2)).right.is[T.Comment]
val splits = baseSplits.map { s =>
- if (willBreak || s.modification.isNewline) s.withIndent(indent)
- else s.andThenPolicyOpt(delayedBreakPolicy)
+ if (willBreak || s.isNL) s.withIndent(indent)
+ else s.andFirstPolicyOpt(delayedBreakPolicyOpt)
}
if (prevSelect.isEmpty) splits
else baseSplits ++ splits.map(_.onlyFor(SplitTag.SelectChainFirstNL))
- case FormatToken(T.Ident(name), _: T.Dot, _) if isSymbolicName(name) =>
- Seq(Split(NoSplit, 0))
-
- case FormatToken(_: T.Underscore, _: T.Dot, _) =>
- Seq(Split(NoSplit, 0))
-
- case tok @ FormatToken(left, dot @ T.Dot() `:chain:` chain, _) =>
- val nestedPenalty = nestedSelect(rightOwner) + nestedApplies(leftOwner)
- val optimalToken = getSelectOptimalToken(chain.last)
- val expire =
- if (chain.length == 1) lastToken(chain.last)
- else optimalToken
-
- val breakOnEveryDot = Policy(expire) {
- case Decision(t @ FormatToken(_, _: T.Dot, _), _)
- if chain.contains(t.meta.rightOwner) =>
- val noNL = style.optIn.breaksInsideChains && t.noBreak
- Seq(Split(NoSplit.orNL(noNL), 1))
- }
- val exclude = getExcludeIf(expire)
- // This policy will apply to both the space and newline splits, otherwise
- // the newline is too cheap even it doesn't actually prevent other newlines.
- val penalizeNewlinesInApply = penalizeAllNewlines(expire, 2)
- val noSplitPolicy =
- SingleLineBlock(expire, exclude).andThen(penalizeNewlinesInApply)
- val newlinePolicy = breakOnEveryDot.andThen(penalizeNewlinesInApply)
- val ignoreNoSplit =
- style.optIn.breakChainOnFirstMethodDot && tok.hasBreak
- val chainLengthPenalty =
- if (
- style.newlines.penalizeSingleSelectMultiArgList &&
- chain.length < 2
- ) {
- // penalize by the number of arguments in the rhs open apply.
- // I know, it's a bit arbitrary, but my manual experiments seem
- // to show that it produces OK output. The key insight is that
- // many arguments on the same line can be hard to read. By not
- // putting a newline before the dot, we force the argument list
- // to break into multiple lines.
- splitCallIntoParts.lift(tokens(tok, 2).meta.rightOwner) match {
- case Some((_, Left(args))) =>
- Math.max(0, args.length - 1)
- case Some((_, Right(argss))) =>
- Math.max(0, argss.map(_.length).sum - 1)
- case _ => 0
- }
- } else 0
- val nlCost = 2 + nestedPenalty + chainLengthPenalty
- Seq(
- Split(NoSplit, 0)
- .notIf(ignoreNoSplit)
- .withPolicy(noSplitPolicy),
- Split(NewlineT(acceptNoSplit = true), nlCost)
- .withPolicy(newlinePolicy)
- .withIndent(2, optimalToken, After)
- )
-
// ApplyUnary
case tok @ FormatToken(T.Ident(_), Literal(), _)
if leftOwner == rightOwner =>
@@ -1328,7 +1402,7 @@ class Router(formatOps: FormatOps) {
.orElse(template.map(_.tokens.last))
.getOrElse(rightOwner.tokens.last)
binPackParentConstructorSplits(
- template.toSet,
+ template.toLeft(Seq.empty),
lastToken,
style.continuationIndent.extendSite
)
@@ -1346,7 +1420,7 @@ class Router(formatOps: FormatOps) {
} =>
splitWithChain(
isFirstWith(template),
- Set(template),
+ Left(template),
templateCurly(template).getOrElse(template.tokens.last)
)
@@ -1359,7 +1433,7 @@ class Router(formatOps: FormatOps) {
val policy =
if (hasSelfAnnotation) NoPolicy
else
- Policy(expire) {
+ Policy.after(expire) {
// Force template to be multiline.
case d @ Decision(
t @ FormatToken(_: T.LeftBrace, right, _),
@@ -1380,7 +1454,7 @@ class Router(formatOps: FormatOps) {
case t @ WithChain(top) =>
splitWithChain(
!t.lhs.is[Type.With],
- withChain(top).toSet,
+ Right(withChain(top)),
top.tokens.last
)
@@ -1401,7 +1475,7 @@ class Router(formatOps: FormatOps) {
Split(Newline, 1)
.withIndent(style.continuationIndent.callSite, close, Before)
.withPolicy(penalizeNewlines)
- .andThenPolicy(newlinesOnlyBeforeClosePolicy(close))
+ .andPolicy(decideNewlinesOnlyBeforeClose(close))
)
else {
val indent: Length =
@@ -1426,7 +1500,7 @@ class Router(formatOps: FormatOps) {
val breakOnlyBeforeElse =
if (elses.isEmpty) Policy.NoPolicy
else
- Policy(expire) {
+ Policy.on(elses.last) {
case d @ Decision(FormatToken(_, r: T.KwElse, _), _)
if elses.contains(r) =>
d.onlyNewlinesWithFallback(Split(Newline, 0))
@@ -1449,10 +1523,11 @@ class Router(formatOps: FormatOps) {
}
val noSpace = isSingleLineComment(right) || shouldBreak(formatToken)
def exclude = insideBlockRanges[T.LeftBrace](formatToken, expire)
+ val noSyntaxNL = leftOwner.is[Term.ForYield] && right.is[T.KwYield]
Seq(
Split(Space, 0)
.notIf(noSpace)
- .withPolicy(SingleLineBlock(expire, exclude = exclude)),
+ .withSingleLineNoOptimal(expire, exclude, noSyntaxNL = noSyntaxNL),
Split(Newline, 1).withIndent(2, expire, After)
)
case FormatToken(T.RightBrace(), T.KwElse(), _) =>
@@ -1470,10 +1545,11 @@ class Router(formatOps: FormatOps) {
val expire = rhsOptimalToken(tokens(rightOwner.tokens.last))
val noSpace = shouldBreak(formatToken)
def exclude = insideBlockRanges[T.LeftBrace](formatToken, expire)
+ val noSyntaxNL = formatToken.right.is[T.KwYield]
Seq(
Split(Space, 0)
.notIf(noSpace)
- .withPolicy(SingleLineBlock(expire, exclude = exclude)),
+ .withSingleLineNoOptimal(expire, exclude, noSyntaxNL = noSyntaxNL),
Split(Newline, 1)
)
// Last else branch
@@ -1484,7 +1560,7 @@ class Router(formatOps: FormatOps) {
val expire = leftOwner.asInstanceOf[Term.If].elsep.tokens.last
val noSpace = shouldBreak(formatToken)
Seq(
- Split(Space, 0).notIf(noSpace).withPolicy(SingleLineBlock(expire)),
+ Split(Space, 0).notIf(noSpace).withSingleLineNoOptimal(expire),
Split(Newline, 1).withIndent(2, expire, After)
)
@@ -1500,7 +1576,7 @@ class Router(formatOps: FormatOps) {
)
case FormatToken(open: T.LeftParen, right, _) =>
- val isConfig = opensConfigStyle(formatToken, false)
+ val isConfig = couldUseConfigStyle(formatToken)
val close = matching(open)
val editionActive = style.activeForEdition_2020_03 ||
!style.newlines.sourceIn(Newlines.classic)
@@ -1514,13 +1590,13 @@ class Router(formatOps: FormatOps) {
Split(Space(useSpace), 0).withIndent(indent, close, After)
}
def spaceSplit =
- spaceSplitWithoutPolicy.withPolicy(penalizeAllNewlines(close, 1))
+ spaceSplitWithoutPolicy.withPolicy(PenalizeAllNewlines(close, 1))
def newlineSplit(cost: Int, forceDangle: Boolean) = {
val shouldDangle = forceDangle ||
editionActive && style.danglingParentheses.callSite
val policy =
if (!shouldDangle) NoPolicy
- else newlinesOnlyBeforeClosePolicy(close)
+ else decideNewlinesOnlyBeforeClose(close)
Split(Newline, cost)
.withPolicy(policy)
.withIndent(style.continuationIndent.callSite, close, Before)
@@ -1549,7 +1625,7 @@ class Router(formatOps: FormatOps) {
else Some(getSingleLineInfixPolicy(close))
spaceSplitWithoutPolicy
.withSingleLine(close)
- .andThenPolicyOpt(singleLineInfixPolicy)
+ .andPolicyOpt(singleLineInfixPolicy)
},
newlineSplit(10, true)
)
@@ -1577,7 +1653,7 @@ class Router(formatOps: FormatOps) {
Split(Space, 0).withSingleLine(expire, killOnFail = true),
Split(Space, 1)
.withPolicy(
- Policy(expire) {
+ Policy.on(expire) {
case d @ Decision(t @ FormatToken(`arrow`, right, _), _)
// TODO(olafur) any other corner cases?
if !right.isInstanceOf[T.LeftBrace] &&
@@ -1593,10 +1669,9 @@ class Router(formatOps: FormatOps) {
case tok @ FormatToken(_, cond @ T.KwIf(), _) if rightOwner.is[Case] =>
val arrow = getCaseArrow(rightOwner.asInstanceOf[Case]).left
val exclude = insideBlockRanges[T.LeftBrace](tok, arrow)
- val singleLine = SingleLineBlock(arrow, exclude = exclude)
Seq(
- Split(Space, 0, policy = singleLine),
+ Split(Space, 0).withSingleLineNoOptimal(arrow, exclude = exclude),
Split(Newline, 1).withPolicy(penalizeNewlineByNesting(cond, arrow))
)
@@ -1619,12 +1694,18 @@ class Router(formatOps: FormatOps) {
}
// Inline comment
- case FormatToken(left, c: T.Comment, _) =>
- val mod =
- if (formatToken.hasBreak && blankLineBeforeDocstring(left, c))
- Newline2x
- else getMod(formatToken)
- Seq(Split(mod, 0))
+ case FormatToken(left, _: T.Comment, _) =>
+ val forceBlankLine = formatToken.hasBreak &&
+ blankLineBeforeDocstring(formatToken)
+ val mod = if (forceBlankLine) Newline2x else getMod(formatToken)
+ val indent = formatToken.meta.rightOwner match {
+ case ts: Term.Select
+ if !left.is[T.Comment] &&
+ findPrevSelect(ts, style.encloseSelectChains).isEmpty =>
+ Indent(2, nextNonComment(next(formatToken)).left, ExpiresOn.After)
+ case _ => Indent.Empty
+ }
+ Seq(Split(mod, 0).withIndent(indent))
// Commented out code should stay to the left
case FormatToken(c: T.Comment, _, _) if isSingleLineComment(c) =>
Seq(Split(Newline, 0))
@@ -1633,6 +1714,7 @@ class Router(formatOps: FormatOps) {
case FormatToken(_: T.KwImplicit, _, _)
if style.activeForEdition_2020_03 &&
+ !style.binPack.unsafeDefnSite &&
!style.verticalMultiline.atDefnSite =>
opensImplicitParamList(prevNonComment(prev(formatToken))).fold {
Seq(Split(Space, 0))
@@ -1734,19 +1816,15 @@ class Router(formatOps: FormatOps) {
val lastToken = leftOwner.asInstanceOf[Term.ForYield].body.tokens.last
Seq(
// Either everything fits in one line or break on =>
- Split(Space, 0).withPolicy(SingleLineBlock(lastToken)),
+ Split(Space, 0).withSingleLineNoOptimal(lastToken),
Split(Newline, 1).withIndent(2, lastToken, After)
)
}
// Interpolation
- case FormatToken(_, T.Interpolation.Id(_) | T.Xml.Start(), _) =>
+ case FormatToken(_, _: T.Interpolation.Id, _) =>
Seq(
Split(Space, 0)
)
- case FormatToken(T.Interpolation.Id(_) | T.Xml.Start(), _, _) =>
- Seq(
- Split(NoSplit, 0)
- )
// Throw exception
case FormatToken(T.KwThrow(), _, _) =>
Seq(
@@ -1760,7 +1838,7 @@ class Router(formatOps: FormatOps) {
)
// seq to var args foo(seq:_*)
case FormatToken(T.Colon(), T.Underscore(), _)
- if next(formatToken).right.syntax == "*" =>
+ if next(formatToken).meta.right.text == "*" =>
Seq(
Split(Space, 0)
)
@@ -1769,7 +1847,24 @@ class Router(formatOps: FormatOps) {
Seq(
Split(NoSplit, 0)
)
+
// Xml
+ case FormatToken(_, _: T.Xml.Start, _) =>
+ Seq(
+ Split(Space, 0)
+ )
+ case FormatToken(open: T.Xml.Start, _, _) =>
+ val splits = Seq(Split(NoSplit, 0))
+ if (prev(formatToken).left.is[T.LeftBrace])
+ splits
+ else
+ withIndentOnXmlStart(open, splits)
+ case FormatToken(_: T.Xml.SpliceStart, _, _)
+ if style.xmlLiterals.assumeFormatted =>
+ withIndentOnXmlSpliceStart(
+ formatToken,
+ Seq(Split(NoSplit, 0))
+ )
case FormatToken(T.Xml.Part(_), _, _) =>
Seq(
Split(NoSplit, 0)
@@ -1778,6 +1873,7 @@ class Router(formatOps: FormatOps) {
Seq(
Split(NoSplit, 0)
)
+
// Fallback
case FormatToken(_, T.Dot(), _) =>
Seq(
@@ -1863,15 +1959,12 @@ class Router(formatOps: FormatOps) {
// TODO(olafur) refactor into "global policy"
// Only newlines after inline comments.
case FormatToken(c: T.Comment, _, _) if isSingleLineComment(c) =>
- val newlineSplits = splits.filter(_.modification.isNewline)
+ val newlineSplits = splits.filter(_.isNL)
if (newlineSplits.isEmpty) Seq(Split(Newline, 0))
else newlineSplits
case FormatToken(_, c: T.Comment, _)
if isAttachedSingleLineComment(formatToken) =>
- splits.map(x =>
- if (x.modification.isNewline) x.copy(modification = Space)(x.line)
- else x
- )
+ splits.map(x => if (x.isNL) x.withMod(Space) else x)
case _ => splits
}
}
@@ -1880,9 +1973,9 @@ class Router(formatOps: FormatOps) {
private def splitWithChain(
isFirstWith: Boolean,
- chain: => Set[Tree],
+ chain: => Either[Template, Seq[Type.With]],
lastToken: => Token
- ): Seq[Split] =
+ )(implicit line: sourcecode.Line, style: ScalafmtConfig): Seq[Split] =
if (isFirstWith) {
binPackParentConstructorSplits(chain, lastToken, IndentForWithChains)
} else {
@@ -1922,12 +2015,9 @@ class Router(formatOps: FormatOps) {
Seq(Split(Space, 0))
else if (isSingleLineComment(ft.right))
Seq(Split(Newline, 0).withIndent(2, expire, After))
- else if (isJsNative(ft.right)) {
- val spacePolicy =
- if (!style.newlines.alwaysBeforeMultilineDef) Policy.NoPolicy
- else SingleLineBlock(expire, exclude = exclude)
- Seq(Split(Space, 0, policy = spacePolicy))
- } else {
+ else if (isJsNative(body))
+ Seq(Split(Space, 0).withSingleLine(expire))
+ else {
val spacePolicy = style.newlines.source match {
case Newlines.classic =>
if (ft.hasBreak) null
@@ -1950,11 +2040,11 @@ class Router(formatOps: FormatOps) {
case _: Term.Try | _: Term.TryWithHandler =>
SingleLineBlock(expire)
case t: Term.If =>
- if (t.elsep.tokens.isEmpty)
+ if (ifWithoutElse(t))
SingleLineBlock(t.cond.tokens.last)
else
SingleLineBlock(expire)
- case _ => penalizeAllNewlines(expire, 1)
+ case _ => PenalizeAllNewlines(expire, 1)
}
}
Seq(
@@ -1962,7 +2052,7 @@ class Router(formatOps: FormatOps) {
Split(Newline, 1)
.withIndent(2, expire, After)
.withPolicy(
- penalizeAllNewlines(expire, 1),
+ PenalizeAllNewlines(expire, 1),
!style.newlines.sourceIgnored
)
)
@@ -2003,14 +2093,14 @@ class Router(formatOps: FormatOps) {
.withPolicy {
val excludeRanges =
insideBlockRanges[T.LeftBrace](ft, expire)
- penalizeAllNewlines(
+ PenalizeAllNewlines(
expire,
Constants.ShouldBeSingleLine,
ignore = x => excludeRanges.exists(_.contains(x.left.start))
)
}
)
- val okNewline = !isJsNative(ft.right)
+ val okNewline = !isJsNative(body)
val spaceSplit = (style.newlines.source match {
case Newlines.classic
if okNewline && ft.hasBreak && ft.meta.leftOwner.is[Defn] =>
@@ -2040,7 +2130,7 @@ class Router(formatOps: FormatOps) {
body match {
case t: Term.If =>
Either.cond(
- t.elsep.tokens.nonEmpty,
+ !ifWithoutElse(t),
SingleLineBlock(expire),
baseSpaceSplit.withSingleLine(t.cond.tokens.last)
)
@@ -2052,7 +2142,7 @@ class Router(formatOps: FormatOps) {
val exclude = insideBlockRanges[LeftParenOrBrace](ft, end)
Right(SingleLineBlock(end, exclude = exclude))
case _ =>
- val policy = penalizeAllNewlines(expire, 1)
+ val policy = PenalizeAllNewlines(expire, 1)
Left(baseSpaceSplit.withOptimalToken(optimal).withPolicy(policy))
}
@@ -2066,11 +2156,11 @@ class Router(formatOps: FormatOps) {
case _: Term.ForYield =>
// unfold policy on yield forces a break
// revert it if we are attempting a single line
- val noBreakOnYield: Policy.Pf = {
+ val noBreakOnYield = Policy.before(expire) {
case Decision(ft, s) if s.isEmpty && ft.right.is[Token.KwYield] =>
Seq(Split(Space, 0))
}
- Right(SingleLineBlock(expire).andThen(noBreakOnYield))
+ Right(SingleLineBlock(expire) & noBreakOnYield)
// we force newlines in try/catch/finally
case _: Term.Try | _: Term.TryWithHandler => Left(Split.ignored)
// don't tuck curried apply
@@ -2090,7 +2180,7 @@ class Router(formatOps: FormatOps) {
.onlyIf(okNewline || spaceSplit.isIgnored)
.withIndent(2, expire, After)
.withPolicy(
- penalizeAllNewlines(expire, 1),
+ PenalizeAllNewlines(expire, 1),
!style.newlines.sourceIgnored
)
)
@@ -2129,7 +2219,7 @@ class Router(formatOps: FormatOps) {
case Newlines.fold | Newlines.classic =>
postCommentFT.meta.rightOwner match {
case _: Term.Try | _: Term.TryWithHandler => Split.ignored
- case t: Term.If if t.elsep.tokens.nonEmpty => Split.ignored
+ case t: Term.If if !ifWithoutElse(t) => Split.ignored
case t: Term.If =>
Split(Space, 1).withSingleLine(t.cond.tokens.last)
case _ =>
@@ -2183,7 +2273,7 @@ class Router(formatOps: FormatOps) {
* else doAnything
* }
* ```
- * */
+ */
Indent(Num(1), expire, After)
)
} else {
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala
index 0455b63225..b0b6ec8cd7 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/Split.scala
@@ -3,9 +3,12 @@ package org.scalafmt.internal
import scala.meta.tokens.Token
import org.scalafmt.internal.Policy.NoPolicy
+import org.scalafmt.util.PolicyOps
import org.scalafmt.util.TokenOps
-case class OptimalToken(token: Token, killOnFail: Boolean = false)
+case class OptimalToken(token: Token, killOnFail: Boolean = false) {
+ override def toString: String = s"$token:${token.end}"
+}
/**
* A Split is the whitespace between two non-whitespace tokens.
@@ -26,56 +29,78 @@ case class OptimalToken(token: Token, killOnFail: Boolean = false)
* even if it exceeds the maxColumn margins, because a secondary split
* was deemed unlikely to win and moved to a backup priority queue.
*
- * @param modification Is this a space, no space, newline or 2 newlines?
+ * @param modExt whitespace and indents
* @param cost How good is this output? Lower is better.
- * @param indents Does this add indentation?
* @param policy How does this split affect other later splits?
* @param line For debugging, to retrace from which case in [[Router]]
* this split originates.
- *
*/
case class Split(
- modification: Modification,
+ modExt: ModExt,
cost: Int,
- tag: SplitTag = SplitTag.Active,
- activeTag: SplitTag = SplitTag.Active,
- indents: Seq[Indent] = Seq.empty,
+ neededTags: Set[SplitTag] = Set.empty,
+ activeTags: Set[SplitTag] = Set.empty,
policy: Policy = NoPolicy,
optimalAt: Option[OptimalToken] = None
)(implicit val line: sourcecode.Line) {
+ import PolicyOps._
import TokenOps._
def adapt(formatToken: FormatToken): Split =
- modification match {
+ modExt.mod match {
case n: NewlineT if !n.noIndent && rhsIsCommentedOut(formatToken) =>
- copy(modification = NewlineT(n.isDouble, noIndent = true))
+ copy(modExt = modExt.copy(mod = NewlineT(n.isDouble, noIndent = true)))
case _ => this
}
- val indentation = indents.mkString("[", ", ", "]")
+ @inline
+ def indentation: String = modExt.indentation
+
+ @inline
+ def isNL: Boolean = modExt.mod.isNewline
+
+ @inline
+ def length: Int = modExt.mod.length
@inline
- def length: Int = modification.length
+ def isIgnored: Boolean = neededTags eq Split.ignoredTags
@inline
- def isIgnored: Boolean = tag eq SplitTag.Ignored
+ def isActive: Boolean = neededTags == activeTags
@inline
- def isActive: Boolean = tag eq activeTag
+ def isActiveFor(splitTag: SplitTag): Boolean = activeTags(splitTag)
+
+ @inline
+ def isNeededFor(splitTag: SplitTag): Boolean = neededTags(splitTag)
+
+ private def ignored: Split =
+ if (isIgnored) this else copy(neededTags = Split.ignoredTags)
@inline
def notIf(flag: Boolean): Split = onlyIf(!flag)
- def onlyIf(flag: Boolean): Split =
- if (flag || isIgnored) this else copy(tag = SplitTag.Ignored)
+ @inline
+ def onlyIf(flag: Boolean): Split = if (flag) this else ignored
+
+ def onlyFor(splitTag: SplitTag, ignore: Boolean = false): Split =
+ if (isIgnored || ignore || isNeededFor(splitTag)) this
+ else copy(neededTags = neededTags + splitTag)(line = line)
+
+ def activateFor(splitTag: SplitTag): Split =
+ if (isIgnored || isActiveFor(splitTag)) this
+ else copy(activeTags = activeTags + splitTag)(line = line)
- def onlyFor(tag: SplitTag, ignore: Boolean = false): Split =
- if (isIgnored || ignore || (this.tag eq tag)) this
- else if (isActive) copy(tag = tag)
- else throw new UnsupportedOperationException("Multiple tags unsupported")
+ def preActivateFor(splitTag: SplitTag): Split =
+ if (isIgnored) this
+ else
+ copy(
+ activeTags = activeTags + splitTag,
+ neededTags = neededTags + splitTag
+ )(line = line)
- def activateFor(tag: SplitTag): Split =
- if (isIgnored || (this.activeTag eq tag)) this else copy(activeTag = tag)
+ def preActivateFor(splitTag: Option[SplitTag]): Split =
+ if (isIgnored) this else splitTag.fold(this)(preActivateFor)
def withOptimalTokenOpt(
token: => Option[Token],
@@ -95,113 +120,136 @@ case class Split(
newPolicy: => Policy,
ignore: Boolean = false
)(implicit line: sourcecode.Line): Split = {
- if (policy != NoPolicy)
- throw new UnsupportedOperationException("Can't have two policies yet.")
+ if (!policy.isEmpty)
+ throw new UnsupportedOperationException("Use orPolicy or andPolicy")
if (isIgnored || ignore) this else copy(policy = newPolicy)
}
def withSingleLine(
expire: Token,
exclude: => Set[Range] = Set.empty,
+ noSyntaxNL: Boolean = false,
killOnFail: Boolean = false
)(implicit line: sourcecode.Line): Split =
- withSingleLineAndOptimal(expire, expire, exclude, killOnFail)
+ withSingleLineAndOptimal(expire, expire, exclude, noSyntaxNL, killOnFail)
def withSingleLineOpt(
expire: Option[Token],
exclude: => Set[Range] = Set.empty,
+ noSyntaxNL: Boolean = false,
killOnFail: Boolean = false
)(implicit line: sourcecode.Line): Split =
- expire.fold(this)(withSingleLine(_, exclude, killOnFail))
+ expire.fold(this)(withSingleLine(_, exclude, noSyntaxNL, killOnFail))
def withSingleLineAndOptimal(
expire: Token,
optimal: Token,
exclude: => Set[Range] = Set.empty,
+ noSyntaxNL: Boolean = false,
killOnFail: Boolean = false
)(implicit line: sourcecode.Line): Split =
withOptimalToken(optimal, killOnFail)
- .withSingleLineNoOptimal(expire, exclude)
+ .withSingleLineNoOptimal(expire, exclude, noSyntaxNL)
def withSingleLineNoOptimal(
expire: Token,
- exclude: => Set[Range] = Set.empty
+ exclude: => Set[Range] = Set.empty,
+ noSyntaxNL: Boolean = false
)(implicit line: sourcecode.Line): Split =
- withPolicy(SingleLineBlock(expire, exclude))
+ withPolicy(
+ SingleLineBlock(
+ expire,
+ exclude,
+ penaliseNewlinesInsideTokens = noSyntaxNL
+ )
+ )
def withPolicyOpt(
newPolicy: => Option[Policy]
)(implicit line: sourcecode.Line): Split =
if (isIgnored) this else newPolicy.fold(this)(withPolicy(_))
- def orElsePolicy(newPolicy: Policy): Split =
+ def orPolicy(newPolicy: Policy): Split =
if (isIgnored || newPolicy.isEmpty) this
- else if (policy.isEmpty) copy(policy = newPolicy)
- else copy(policy = policy.orElse(newPolicy))
+ else copy(policy = policy | newPolicy)(line = line)
- def andThenPolicy(newPolicy: Policy): Split =
+ def andPolicy(newPolicy: Policy): Split =
if (isIgnored || newPolicy.isEmpty) this
- else if (policy.isEmpty) copy(policy = newPolicy)
- else copy(policy = policy.andThen(newPolicy))
+ else copy(policy = policy & newPolicy)(line = line)
- def andThenPolicyOpt(newPolicy: => Option[Policy]): Split =
- if (isIgnored) this
- else newPolicy.fold(this)(andThenPolicy)
+ def andPolicy(newPolicy: => Policy, ignore: Boolean): Split =
+ if (ignore) this else andPolicy(newPolicy)
+
+ def andPolicyOpt(newPolicy: => Option[Policy]): Split =
+ if (isIgnored) this else newPolicy.fold(this)(andPolicy)
+
+ def andFirstPolicy(newPolicy: Policy): Split =
+ if (isIgnored || newPolicy.isEmpty) this
+ else copy(policy = newPolicy & policy)(line = line)
+
+ def andFirstPolicyOpt(newPolicy: => Option[Policy]): Split =
+ if (isIgnored) this else newPolicy.fold(this)(andFirstPolicy)
def withPenalty(penalty: Int): Split =
- if (isIgnored) this else copy(cost = cost + penalty)
+ if (isIgnored || penalty <= 0) this else copy(cost = cost + penalty)
def withIndent(length: => Length, expire: => Token, when: ExpiresOn): Split =
- if (isIgnored) this
- else
- length match {
- case Length.Num(0) => this
- case x => withIndentImpl(Indent(x, expire, when))
- }
+ withMod(modExt.withIndent(length, expire, when))
def withIndentOpt(
length: => Length,
expire: Option[Token],
when: ExpiresOn
): Split =
- expire.fold(this)(withIndent(length, _, when))
+ withMod(modExt.withIndentOpt(length, expire, when))
def withIndent(indent: => Indent): Split =
- if (isIgnored) this
- else
- indent match {
- case Indent.Empty => this
- case x => withIndentImpl(x)
- }
+ withMod(modExt.withIndent(indent))
def withIndentOpt(indent: => Option[Indent]): Split =
- if (isIgnored) this
- else indent.fold(this)(withIndent(_))
+ withMod(modExt.withIndentOpt(indent))
def withIndents(indents: Seq[Indent]): Split =
- indents.foldLeft(this)(_ withIndent _)
+ withMod(modExt.withIndents(indents))
- private def withIndentImpl(indent: Indent): Split =
- copy(indents = indent +: indents)
+ def switch(switchObject: AnyRef): Split =
+ withMod(modExt.switch(switchObject))
- def switch(switchObject: AnyRef): Split = {
- val newIndents = indents.map(_.switch(switchObject))
- copy(indents = newIndents.filter(_ ne Indent.Empty))
- }
+ def withMod(mod: Modification): Split =
+ if (this.modExt.mod eq mod) this else withMod(modExt.copy(mod = mod))
- override def toString = {
- val prefix = tag match {
- case SplitTag.Ignored => "!"
- case SplitTag.Active => ""
- case _ => s"[$tag]"
+ def withMod(modExtByName: => ModExt): Split =
+ if (isIgnored) this
+ else {
+ val modExt = modExtByName
+ if (this.modExt eq modExt) this else copy(modExt = modExt)(line = line)
}
- s"""$prefix$modification:${line.value}(cost=$cost, indents=$indentation, $policy)"""
+
+ override def toString = {
+ val prefix =
+ if (isIgnored) "!"
+ else {
+ val wantedTags = neededTags.filterNot(activeTags).mkString(",")
+ val unusedTags = activeTags.filterNot(neededTags).mkString(",")
+ if (unusedTags.nonEmpty) s"[$wantedTags!$unusedTags]"
+ else if (wantedTags.nonEmpty) s"[$wantedTags]"
+ else ""
+ }
+ val opt = optimalAt.fold("")(", opt=" + _)
+ s"""$prefix${modExt.mod}:${line.value}(cost=$cost, indents=$indentation, $policy$opt)"""
}
}
object Split {
+ private val ignoredTags = Set[SplitTag](null)
+
def ignored(implicit line: sourcecode.Line) =
- Split(NoSplit, 0, tag = SplitTag.Ignored)
+ Split(ModExt(NoSplit), 0).ignored
+
+ def apply(ignore: Boolean, cost: Int)(
+ modExt: ModExt
+ )(implicit line: sourcecode.Line): Split =
+ if (ignore) ignored else Split(modExt, cost)
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SplitTag.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SplitTag.scala
index e32c47e29e..9f1668ef48 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SplitTag.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/SplitTag.scala
@@ -2,26 +2,17 @@ package org.scalafmt.internal
sealed abstract class SplitTag {
- def activateOnly(splits: Seq[Split]): Seq[Split]
+ final def activateOnly(splits: Seq[Split]): Seq[Split] =
+ splits.map(_.activateFor(this))
}
object SplitTag {
- abstract class Base extends SplitTag {
- override final def activateOnly(splits: Seq[Split]): Seq[Split] = splits
- }
-
- abstract class Custom extends SplitTag {
- override final def activateOnly(splits: Seq[Split]): Seq[Split] =
- splits.map(_.activateFor(this)).filter(_.isActive)
- }
-
- case object Active extends Base
- case object Ignored extends Base
-
- case object OneArgPerLine extends Custom
- case object SelectChainFirstNL extends Custom
- case object InfixChainNoNL extends Custom
+ case object OneArgPerLine extends SplitTag
+ case object SelectChainFirstNL extends SplitTag
+ case object SelectChainSecondNL extends SplitTag
+ case object InfixChainNoNL extends SplitTag
+ case object OnelineWithChain extends SplitTag
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala
index 2900c97c1e..d9a4f16f35 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala
@@ -1,11 +1,16 @@
package org.scalafmt.internal
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
import scala.annotation.tailrec
import scala.meta.tokens.Token
-import scala.meta.tokens.Token.Comment
+import org.scalafmt.config.Comments
+import org.scalafmt.config.Docstrings
import org.scalafmt.config.ScalafmtConfig
import org.scalafmt.util.TokenOps
+import org.scalafmt.util.TreeOps
/**
* A partial formatting solution up to splits.length number of tokens.
@@ -17,8 +22,10 @@ final case class State(
depth: Int,
prev: State,
indentation: Int,
- pushes: Vector[ActualIndent],
+ pushes: Seq[ActualIndent],
column: Int,
+ allAltAreNL: Boolean,
+ delayedPenalty: Int, // apply if positive, ignore otherwise
formatOff: Boolean
) {
@@ -31,79 +38,228 @@ final case class State(
* Calculates next State given split at tok.
*/
def next(
- style: ScalafmtConfig,
- split: Split,
- tok: FormatToken
- ): State = {
- val newIndents: Vector[ActualIndent] =
- if (tok.right.is[Token.EOF]) Vector.empty
+ initialNextSplit: Split,
+ nextAllAltAreNL: Boolean
+ )(implicit style: ScalafmtConfig, fops: FormatOps): State = {
+ val tok = fops.tokens(depth)
+ val right = tok.right
+
+ val (nextSplit, nextIndent, nextIndents) =
+ if (right.is[Token.EOF]) (initialNextSplit, 0, Seq.empty)
else {
val offset = column - indentation
- val newPushes = split.indents.flatMap(_.withStateOffset(offset))
- (pushes ++ newPushes).filter(_.notExpiredBy(tok))
+ def getUnexpired(indents: Seq[ActualIndent]): Seq[ActualIndent] =
+ indents.filter(_.notExpiredBy(tok))
+ def getPushes(indents: Seq[Indent]): Seq[ActualIndent] =
+ getUnexpired(indents.flatMap(_.withStateOffset(offset)))
+ val indents = initialNextSplit.modExt.indents
+ val nextPushes = getUnexpired(pushes) ++ getPushes(indents)
+ val nextIndent = Indent.getIndent(nextPushes)
+ initialNextSplit.modExt.mod match {
+ case m: NewlineT
+ if !tok.left.is[Token.Comment] && m.alt.isDefined &&
+ nextIndent >= m.alt.get.mod.length + column =>
+ val alt = m.alt.get
+ val altPushes = getPushes(alt.indents)
+ val altIndent = Indent.getIndent(altPushes)
+ val split = initialNextSplit.withMod(alt.withIndents(indents))
+ (split, nextIndent + altIndent, nextPushes ++ altPushes)
+ case _ =>
+ (initialNextSplit, nextIndent, nextPushes)
+ }
}
- val newIndent = newIndents.foldLeft(0)(_ + _.length)
-
- val tokRightSyntax = tok.right.syntax
- // Always account for the cost of the right token.
- val tokLength = tokRightSyntax.length
// Some tokens contain newline, like multiline strings/comments.
- val lengthOnFirstLine = TokenOps.tokenLength(tok.right)
- val columnOnCurrentLine =
- lengthOnFirstLine + {
- if (split.modification.isNewline) newIndent
- else column + split.length
- }
- val lengthOnLastLine = {
- val lastNewline = tokRightSyntax.lastIndexOf('\n')
- if (lastNewline == -1) tokLength
- else tokLength - lastNewline - 1
- }
- val nextStateColumn =
- lengthOnLastLine + {
- // Tokens with newlines get no indentation.
- if (tokRightSyntax.contains('\n')) 0
- else if (split.modification.isNewline) newIndent
- else column + split.length
- }
- val newPolicy: PolicySummary = policy.combine(split.policy, tok.left.end)
- val splitWithPenalty = {
+ val (columnOnCurrentLine, nextStateColumn) = State.getColumns(
+ tok,
+ nextIndent,
+ if (nextSplit.isNL) None else Some(column + nextSplit.length)
+ )
+
+ val overflow = columnOnCurrentLine - style.maxColumn
+ val nextPolicy: PolicySummary =
+ policy.combine(nextSplit.policy, fops.next(tok))
+
+ val (penalty, nextDelayedPenalty) =
if (
- columnOnCurrentLine <= style.maxColumn || {
- val commentExceedsLineLength =
- tok.right.is[Comment] &&
- tokRightSyntax.length >= (style.maxColumn - newIndent)
- commentExceedsLineLength && split.modification.isNewline
+ overflow <= 0 || right.is[Token.Comment] && {
+ val rtext = tok.meta.right.text
+ nextSplit.isNL && rtext.length >= (style.maxColumn - nextIndent) ||
+ fops.next(tok).hasBreak && {
+ if (TokenOps.isDocstring(rtext))
+ (style.docstrings.wrap ne Docstrings.Wrap.no) && nextSplit.isNL
+ else
+ (style.comments.wrap eq Comments.Wrap.trailing) ||
+ (style.comments.wrap ne Comments.Wrap.no) && nextSplit.isNL
+ }
}
) {
- split // fits inside column
+ (math.max(0, delayedPenalty), 0) // fits inside column
} else {
- split.withPenalty(
+ val defaultOverflowPenalty =
Constants.ExceedColumnPenalty + columnOnCurrentLine
- ) // overflow
+ if (style.newlines.avoidForSimpleOverflow.isEmpty)
+ (defaultOverflowPenalty, 0)
+ else
+ getOverflowPenalty(nextSplit, defaultOverflowPenalty)
}
- }
+ val splitWithPenalty = nextSplit.withPenalty(penalty)
val nextFormatOff =
- if (TokenOps.isFormatOff(tok.right)) true
- else if (TokenOps.isFormatOn(tok.right)) false
- else formatOff
+ if (formatOff) !TokenOps.isFormatOn(right)
+ else TokenOps.isFormatOff(right)
State(
cost + splitWithPenalty.cost,
// TODO(olafur) expire policy, see #18.
- newPolicy,
+ nextPolicy,
splitWithPenalty,
depth + 1,
this,
- newIndent,
- newIndents,
+ nextIndent,
+ nextIndents,
nextStateColumn,
+ nextAllAltAreNL,
+ nextDelayedPenalty,
nextFormatOff
)
}
+ /** Returns a penalty to be applied to the split and any delayed penalty.
+ * - if delayedPenalty is positive, it is considered activated and will be
+ * applied at the end of a line unless deactivated earlier;
+ * - if delayedPenalty is negative, it is considered inactive but could be
+ * converted to regular penalty if a disqualifying token/split is found
+ * before the end of a line or document.
+ */
+ @tailrec
+ private def getOverflowPenalty(
+ nextSplit: Split,
+ defaultOverflowPenalty: Int
+ )(implicit style: ScalafmtConfig, fops: FormatOps): (Int, Int) = {
+ val prevActive = delayedPenalty > 0
+ val fullPenalty = defaultOverflowPenalty +
+ (if (prevActive) delayedPenalty else -delayedPenalty)
+ def result(customPenalty: Int, nextActive: Boolean): (Int, Int) = {
+ val delay = fullPenalty - customPenalty
+ val nextDelayedPenalty = // always preserve a little delayed penalty
+ if (delay > 0) delay else if (delayedPenalty == 0) 0 else 1
+ val penalty = fullPenalty - nextDelayedPenalty
+ (penalty, if (nextActive) nextDelayedPenalty else -nextDelayedPenalty)
+ }
+ val ft = fops.tokens(depth)
+ if (nextSplit.isNL || ft.right.is[Token.EOF]) {
+ result(if (prevActive) fullPenalty else defaultOverflowPenalty, false)
+ } else {
+ val tokLength = ft.meta.right.text.length
+ def getFullPenalty = result(fullPenalty, true)
+ def getCustomPenalty = {
+ val isComment = ft.right.is[Token.Comment]
+ /* we only delay penalty for overflow tokens which are part of a
+ * statement that started at the beginning of the current line */
+ val startFtOpt =
+ if (!State.allowSplitForLineStart(nextSplit, ft, isComment)) None
+ else lineStartsStatement(isComment)
+ val delay = startFtOpt.exists {
+ case FormatToken(_, t: Token.Interpolation.Start, _) =>
+ fops.matching(t) ne ft.right
+ case _ => true
+ }
+ // if delaying, estimate column if the split had been a newline
+ if (!delay) getFullPenalty
+ else result(10 + indentation * 3 / 2 + tokLength, false)
+ }
+ if (ft.meta.right.firstNL >= 0) getFullPenalty
+ else if (
+ style.newlines.avoidForSimpleOverflowTooLong &&
+ State.isWithinInterpolation(ft.meta.rightOwner)
+ ) {
+ ft.right match {
+ case _: Token.Interpolation.End => getCustomPenalty
+ case _: Token.Interpolation.Id if delayedPenalty != 0 =>
+ getFullPenalty // can't delay multiple times
+ case _ => // delay for intermediate interpolation tokens
+ result(tokLength, true)
+ }
+ } else if (
+ ft.right.isInstanceOf[Product] &&
+ tokLength == 1 && !ft.meta.right.text.head.isLetterOrDigit
+ ) { // delimiter
+ val ok = delayedPenalty != 0 || {
+ style.newlines.avoidForSimpleOverflowPunct &&
+ column >= style.maxColumn
+ }
+ if (ok) result(1, prevActive)
+ else prev.getOverflowPenalty(split, defaultOverflowPenalty + 1)
+ } else if (
+ style.newlines.avoidForSimpleOverflowTooLong &&
+ delayedPenalty == 0 // can't delay multiple times
+ ) {
+ getCustomPenalty
+ } else {
+ getFullPenalty
+ }
+ }
+ }
+
+ /**
+ * Traverses back to the beginning of the line and returns the largest tree
+ * which starts with that token at the start of the line, if any.
+ * @see [[State.allowSplitForLineStart]] which tokens can be traversed.
+ */
+ @tailrec
+ private def getLineStartOwner(isComment: Boolean)(implicit
+ style: ScalafmtConfig,
+ fops: FormatOps
+ ): Option[(FormatToken, meta.Tree)] = {
+ val ft = fops.tokens(depth)
+ if (ft.meta.left.firstNL >= 0) None
+ else if (!split.isNL) {
+ val ok = (prev ne State.start) &&
+ State.allowSplitForLineStart(split, ft, isComment)
+ if (ok) prev.getLineStartOwner(isComment) else None
+ } else {
+ def startsWithLeft(tree: meta.Tree): Boolean =
+ tree.tokens.headOption.contains(ft.left)
+ val ro = ft.meta.rightOwner
+ val owner =
+ if (startsWithLeft(ro)) Some(ro)
+ else {
+ val lo = ft.meta.leftOwner
+ if (startsWithLeft(lo)) Some(lo) else None
+ }
+ owner.map { x =>
+ val y = x.parent.flatMap { p =>
+ if (!startsWithLeft(p)) None
+ else TreeOps.findTreeWithParentSimple(p, false)(startsWithLeft)
+ }
+ (ft, y.getOrElse(x))
+ }
+ }
+ }
+
+ /**
+ * Check that the current line starts a statement which also contains
+ * the current token.
+ */
+ private def lineStartsStatement(
+ isComment: Boolean
+ )(implicit style: ScalafmtConfig, fops: FormatOps): Option[FormatToken] = {
+ getLineStartOwner(isComment).flatMap {
+ case (lineFt, lineOwner) =>
+ val ft = fops.tokens(depth)
+ val ok = {
+ // comment could be preceded by a comma
+ isComment && ft.left.is[Token.Comma] &&
+ (fops.prev(ft).meta.leftOwner eq lineOwner)
+ } ||
+ TreeOps
+ .findTreeOrParentSimple(ft.meta.leftOwner)(_ eq lineOwner)
+ .isDefined
+ if (ok) Some(lineFt) else None
+ }
+ }
+
}
object State {
@@ -115,7 +271,9 @@ object State {
0,
null,
0,
- Vector.empty[ActualIndent],
+ Seq.empty,
+ 0,
+ false,
0,
formatOff = false
)
@@ -157,4 +315,94 @@ object State {
}
}
+ private val stripMarginPattern =
+ Pattern.compile("\n(\\h*+\\|)?([^\n]*+)")
+
+ def getColumns(
+ ft: FormatToken,
+ indent: Int,
+ noBreakColumn: Option[Int]
+ )(implicit style: ScalafmtConfig): (Int, Int) = {
+ val firstLineOffset = noBreakColumn.getOrElse(indent)
+ val syntax = ft.meta.right.text
+ val firstNewline = ft.meta.right.firstNL
+ if (firstNewline == -1) {
+ val firstLineLength = firstLineOffset + syntax.length
+ (firstLineLength, firstLineLength)
+ } else
+ ft.right match {
+ case _: Token.Constant.String =>
+ getColumnsWithStripMargin(syntax, firstNewline, indent, noBreakColumn)
+ case _ =>
+ val lastNewline = syntax.length - syntax.lastIndexOf('\n') - 1
+ (firstLineOffset + firstNewline, lastNewline)
+ }
+ }
+
+ private def getColumnsWithStripMargin(
+ syntax: String,
+ firstNewline: Int,
+ indent: Int,
+ noBreakColumn: Option[Int]
+ )(implicit style: ScalafmtConfig): (Int, Int) = {
+ val column = noBreakColumn.getOrElse(indent)
+ val matcher = stripMarginPattern.matcher(syntax)
+ matcher.region(firstNewline, syntax.length)
+ val firstLineLength = column + firstNewline
+ if (!matcher.find()) (firstLineLength, firstLineLength)
+ else {
+ val matcherToLength = getMatcherToLength(column, indent, style)
+ @tailrec
+ def iter(prevMaxLength: Int): (Int, Int) = {
+ val length = matcherToLength(matcher)
+ val maxLength = math.max(prevMaxLength, length)
+ if (matcher.find()) iter(maxLength) else (maxLength, length)
+ }
+ iter(firstLineLength)
+ }
+ }
+
+ private def getMatcherToLength(
+ column: Int,
+ indent: Int,
+ style: ScalafmtConfig
+ ): Matcher => Int = {
+ val adjustMargin: Int => Int =
+ if (!style.assumeStandardLibraryStripMargin) identity[Int]
+ else {
+ // 3 for '|' + 2 spaces
+ val adjusted = 3 + (if (style.align.stripMargin) column else indent)
+ _ => adjusted
+ }
+ (matcher: Matcher) => {
+ val margin = matcher.end(1) - matcher.start(1)
+ val textLength = matcher.end(2) - matcher.start(2)
+ // if 0, has newline but no pipe
+ if (0 == margin) textLength else textLength + adjustMargin(margin)
+ }
+ }
+
+ /**
+ * Checks whether a given token and split can be traversed while looking for
+ * the beginning of the line.
+ */
+ private def allowSplitForLineStart(
+ split: Split,
+ ft: FormatToken,
+ isComment: Boolean
+ ): Boolean = {
+ {
+ split.length == 0 || isComment ||
+ ft.meta.leftOwner.is[meta.Term.Assign]
+ } && !split.modExt.indents.exists(_.hasStateColumn)
+ }
+
+ @inline
+ private def isInterpolation(tree: meta.Tree): Boolean =
+ tree.is[meta.Term.Interpolate]
+
+ @inline
+ private def isWithinInterpolation(tree: meta.Tree): Boolean =
+ TreeOps.findTreeOrParentSimple(tree)(isInterpolation).isDefined
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/PreferCurlyFors.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/PreferCurlyFors.scala
index 86f6afa980..56946c6d02 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/PreferCurlyFors.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/PreferCurlyFors.scala
@@ -24,7 +24,6 @@ object PreferCurlyFors extends Rewrite {
* a <- as
* b <- bs if b > 2
* } yield (a, b)
- *
*/
class PreferCurlyFors(implicit ctx: RewriteCtx) extends RewriteSession {
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala
index 80916325fa..957fe00fc4 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantBraces.scala
@@ -232,11 +232,6 @@ class RedundantBraces(implicit ctx: RewriteCtx) extends RewriteSession {
case d: Defn.Def =>
def disqualifiedByUnit =
!settings.includeUnitMethods && d.decltpe.exists(_.syntax == "Unit")
- def innerOk(s: Stat) =
- s match {
- case _: Term.Function | _: Defn => false
- case _ => true
- }
settings.methodBodies &&
getSingleStatIfLineSpanOk(b).exists(innerOk) &&
!isProcedureSyntax(d) &&
@@ -251,6 +246,12 @@ class RedundantBraces(implicit ctx: RewriteCtx) extends RewriteSession {
}
}
+ private def innerOk(s: Stat) =
+ s match {
+ case _: Term.Function | _: Defn => false
+ case _ => true
+ }
+
private def okToRemoveBlockWithinApply(b: Term.Block): Boolean =
getSingleStatIfLineSpanOk(b).exists {
case f: Term.Function =>
@@ -263,7 +264,7 @@ class RedundantBraces(implicit ctx: RewriteCtx) extends RewriteSession {
/** Some blocks look redundant but aren't */
private def shouldRemoveSingleStatBlock(b: Term.Block): Boolean =
getSingleStatIfLineSpanOk(b).exists { stat =>
- !b.parent.exists {
+ innerOk(stat) && !b.parent.exists {
case parentIf: Term.If if stat.is[Term.If] =>
// if (a) { if (b) c } else d
// ↑ cannot be replaced by ↓
@@ -271,7 +272,6 @@ class RedundantBraces(implicit ctx: RewriteCtx) extends RewriteSession {
// which would be equivalent to
// if (a) { if (b) c else d }
def insideIfThen = parentIf.thenp eq b
- def ifWithoutElse(t: Term.If) = t.elsep.is[Lit.Unit]
@tailrec
def existsIfWithoutElse(t: Term.If): Boolean =
t.elsep match {
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala
index 920861c7f6..ac5900e203 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/RedundantParens.scala
@@ -24,7 +24,7 @@ class RedundantParens(implicit ctx: RewriteCtx) extends RewriteSession {
}
private[rewrite] val rewriteFunc: PartialFunction[Tree, Unit] = {
- case t @ (_: Term.Tuple | _: Type.Tuple) => remove(t, 2)
+ case t @ (_: Term.Tuple | _: Type.Tuple | _: Lit.Unit) => remove(t, 2)
case g: Enumerator.Guard => remove(g.cond)
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala
index 501a368ab3..fa773830e1 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala
@@ -37,8 +37,7 @@ class GitOpsImpl(private[util] val workingDirectory: AbsoluteFile)
)
}
}
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- gitRes.map(augmentString(_).lines.toSeq)
+ gitRes.map(_.linesIterator.toSeq)
}
override def lsTree(dir: AbsoluteFile): Seq[AbsoluteFile] = {
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LiteralOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LiteralOps.scala
index 40ca796c3f..fef1672b99 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LiteralOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LiteralOps.scala
@@ -16,7 +16,7 @@ object LiteralOps {
*
* literals.hexPrefix applies prefix, literals.hexDigits applies to body
* and literals.long applies to suffix
- * */
+ */
def prettyPrintInteger(
str: String
)(implicit style: ScalafmtConfig): String =
@@ -43,7 +43,7 @@ object LiteralOps {
* f is a float/double suffix
*
* literals.scientific applies to body and literals.float/double applies to suffix
- * */
+ */
private def prettyPrintFloatingPoint(
str: String,
suffixUpper: Char,
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala
index 2297ca9cf7..b8fb69b500 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/LoggerOps.scala
@@ -23,8 +23,8 @@ object LoggerOps {
styles.map(x => x.source -> x.value).toMap
def log(s: State): String = {
- val policies = s.policy.policies.map(_.toString).mkString(",")
- s"d=${s.depth} w=${s.cost} i=${s.indentation} col=${s.column}; p=$policies; s=${log(s.split)}"
+ val policies = s.policy.policies.map(_.toString).mkString("P[", ",", "]")
+ s"d=${s.depth} w=${s.cost} i=${s.indentation} col=${s.column}; $policies; s=${log(s.split)}"
}
def log(split: Split): String = s"$split"
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala
new file mode 100644
index 0000000000..df41cc9f6f
--- /dev/null
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/PolicyOps.scala
@@ -0,0 +1,66 @@
+package org.scalafmt.util
+
+import scala.meta.tokens.{Token => T}
+
+import org.scalafmt.internal.Decision
+import org.scalafmt.internal.FormatToken
+import org.scalafmt.internal.Policy
+
+object PolicyOps {
+
+ case class PenalizeAllNewlines(
+ expire: T,
+ penalty: Int,
+ penalizeLambdas: Boolean = true,
+ ignore: FormatToken => Boolean = _ => false,
+ penaliseNewlinesInsideTokens: Boolean = false
+ )(implicit line: sourcecode.Line)
+ extends Policy.Clause {
+ override val noDequeue: Boolean = false
+ override val endPolicy: Policy.End.WithPos = Policy.End.Before(expire.end)
+ override val f: Policy.Pf = {
+ case Decision(tok, s)
+ if (penalizeLambdas || !tok.left.is[T.RightArrow]) && !ignore(tok) =>
+ s.map {
+ case split
+ if split.isNL ||
+ (penaliseNewlinesInsideTokens && tok.leftHasNewline) =>
+ split.withPenalty(penalty)
+ case x => x
+ }
+ }
+ override def toString: String = s"PNL:${super.toString}+$penalty"
+ }
+
+ /**
+ * Forces allssplits up to including expire to be on a single line.
+ */
+ case class SingleLineBlock(
+ expire: T,
+ exclude: Set[Range] = Set.empty,
+ disallowSingleLineComments: Boolean = true,
+ penaliseNewlinesInsideTokens: Boolean = false
+ )(implicit line: sourcecode.Line)
+ extends Policy.Clause {
+ import TokenOps.isSingleLineComment
+ private val endPos = expire.end
+ override val noDequeue: Boolean = true
+ override val endPolicy: Policy.End.WithPos = Policy.End.On(endPos)
+ override def toString: String =
+ "SLB:" + super.toString + {
+ if (exclude.isEmpty) ""
+ else exclude.map(x => s"${x.start}:${x.end}").mkString("^{", ",", "}")
+ }
+ override val f: Policy.Pf = {
+ case Decision(tok, s)
+ if !tok.right.is[T.EOF] && tok.right.end <= endPos &&
+ exclude.forall(!_.contains(tok.left.start)) &&
+ (disallowSingleLineComments || !isSingleLineComment(tok.left)) =>
+ if (penaliseNewlinesInsideTokens && tok.leftHasNewline)
+ Seq.empty
+ else
+ s.filterNot(_.isNL)
+ }
+ }
+
+}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala
index 8325c0ac8a..afd0a088e1 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TokenOps.scala
@@ -1,18 +1,16 @@
package org.scalafmt.util
import scala.meta.classifiers.Classifier
-import scala.meta.{Defn, Pkg, Template, Tree}
+import scala.meta.{Defn, Pkg, Source, Template, Term, Tree}
import scala.meta.tokens.Token
import scala.meta.tokens.Token._
import scala.meta.tokens.Tokens
import org.scalafmt.config.Newlines
import org.scalafmt.config.ScalafmtConfig
-import org.scalafmt.internal.Decision
import org.scalafmt.internal.FormatToken
import org.scalafmt.internal.Modification
import org.scalafmt.internal.NewlineT
-import org.scalafmt.internal.Policy
import org.scalafmt.internal.Space
/**
@@ -48,19 +46,22 @@ object TokenOps {
longHash
}
- def blankLineBeforeDocstring(
- left: Token,
- right: Token
- )(implicit style: ScalafmtConfig): Boolean =
- right.is[Token.Comment] &&
- blankLineBeforeDocstring(left, right.asInstanceOf[Token.Comment])
+ def isDocstring(text: String): Boolean =
+ text.startsWith("/**")
def blankLineBeforeDocstring(
- left: Token,
- right: Token.Comment
+ ft: FormatToken
)(implicit style: ScalafmtConfig): Boolean =
style.optIn.forceNewlineBeforeDocstringSummary &&
- !left.is[Token.Comment] && right.syntax.startsWith("/**")
+ ft.right.is[Token.Comment] && !ft.left.is[Token.Comment] &&
+ isDocstring(ft.meta.right.text) &&
+ TreeOps
+ .findTreeOrParent(ft.meta.leftOwner) {
+ case t if t.pos.end <= ft.right.start => None
+ case _: Pkg | _: Source | _: Template | _: Term.Block => Some(false)
+ case _ => Some(true)
+ }
+ .isEmpty
// 2.13 implements SeqOps.findLast
def findLast[A](seq: Seq[A])(cond: A => Boolean): Option[A] =
@@ -84,62 +85,24 @@ object TokenOps {
val booleanOperators = Set("&&", "||")
- // TODO(olafur) more general solution?
- val newlineOkOperators = Set("+", "-", "|")
-
def isBoolOperator(token: Token): Boolean =
booleanOperators.contains(token.syntax)
- def newlineOkOperator(token: Token): Boolean =
- booleanOperators.contains(token.syntax) ||
- newlineOkOperators.contains(token.syntax)
-
def identModification(ident: Ident): Modification = {
val lastCharacter = ident.syntax.last
Space(!Character.isLetterOrDigit(lastCharacter) && lastCharacter != '`')
}
- def isOpenApply(
- token: Token,
- includeCurly: Boolean = false,
- includeNoParens: Boolean = false
- ): Boolean =
- token match {
- case LeftParen() | LeftBracket() => true
- case LeftBrace() if includeCurly => true
- case Dot() if includeNoParens => true
- case _ => false
- }
+ @inline
+ def isSingleLineComment(c: String): Boolean = c.startsWith("//")
- /**
- * Forces allssplits up to including expire to be on a single line.
- */
- def SingleLineBlock(
- expire: Token,
- exclude: Set[Range] = Set.empty,
- disallowSingleLineComments: Boolean = true,
- penaliseNewlinesInsideTokens: Boolean = false
- )(implicit line: sourcecode.Line): Policy = {
- Policy(
- {
- case d @ Decision(tok, _)
- if !tok.right.is[EOF] && tok.right.end <= expire.end &&
- exclude.forall(!_.contains(tok.left.start)) &&
- (disallowSingleLineComments || !isSingleLineComment(tok.left)) =>
- if (penaliseNewlinesInsideTokens && tok.leftHasNewline) {
- Seq.empty
- } else {
- d.noNewlines
- }
- },
- expire.end,
- noDequeue = true
- )
+ @inline
+ def isSingleLineComment(c: Token.Comment): Boolean = {
+ (c.end - c.start) >= 2 &&
+ c.input.chars(c.start) == '/' &&
+ c.input.chars(c.start + 1) == '/'
}
- def isSingleLineComment(c: Token.Comment): Boolean =
- c.syntax.startsWith("//")
-
def isSingleLineComment(token: Token): Boolean =
token match {
case c: Comment => isSingleLineComment(c)
@@ -175,48 +138,37 @@ object TokenOps {
case _ => None
}
- def tokenLength(token: Token): Int =
- token match {
- case lit: Constant.String =>
- // Even if the literal is not strip margined, we use the longest line
- // excluding margins. The will only affect is multiline string literals
- // with a short first line but long lines inside, example:
- //
- // val x = """short
- // Long aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- // """
- //
- // In this case, we would put a newline before """short and indent by
- // two.
- //
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- augmentString(lit.syntax).lines
- .map(_.replaceFirst(" *|", "").length)
- .max
- case _ =>
- val tokenSyntax = token.syntax
- val firstNewline = tokenSyntax.indexOf('\n')
- if (firstNewline == -1) tokenSyntax.length
- else firstNewline
+ def defnBeforeTemplate(tree: Tree): Option[Tree] =
+ tree match {
+ case t: Defn.Object => Some(t.name)
+ case t: Defn.Class => Some(t.ctor)
+ case t: Defn.Trait => Some(t.ctor)
+ case t: Pkg.Object => Some(t.name)
+ case _ => None
}
- def isFormatOn(token: Token): Boolean =
- token match {
- case c: Comment if formatOnCode.contains(c.syntax.toLowerCase) => true
- case _ => false
- }
+ val formatOnCode = Set(
+ "@formatter:on", // IntelliJ
+ "format: on" // scalariform
+ )
+
+ val formatOffCode = Set(
+ "@formatter:off", // IntelliJ
+ "format: off" // scalariform
+ )
- def isFormatOff(token: Token): Boolean =
+ @inline
+ def isFormatOn(token: Token): Boolean = isFormatIn(token, formatOnCode)
+
+ @inline
+ def isFormatOff(token: Token): Boolean = isFormatIn(token, formatOffCode)
+
+ private def isFormatIn(token: Token, set: Set[String]): Boolean =
token match {
- case c: Comment if formatOffCode.contains(c.syntax.toLowerCase) => true
+ case t: Comment => set.contains(t.value.trim.toLowerCase)
case _ => false
}
- val formatOffCode = Set(
- "// @formatter:off", // IntelliJ
- "// format: off" // scalariform
- )
-
def endsWithSymbolIdent(tok: Token): Boolean =
tok match {
case Ident(name) => !name.last.isLetterOrDigit
@@ -236,11 +188,6 @@ object TokenOps {
!head.isLetter && head != '_'
}
- val formatOnCode = Set(
- "// @formatter:on", // IntelliJ
- "// format: on" // scalariform
- )
-
def shouldBreak(ft: FormatToken)(implicit style: ScalafmtConfig): Boolean =
style.newlines.source match {
case Newlines.classic | Newlines.keep => ft.hasBreak
@@ -264,4 +211,14 @@ object TokenOps {
.filter(_.head.tokens.head.start <= math.max(token.end, other.end))
}
+ def getXmlLastLineIndent(tok: Xml.Part): Option[Int] = {
+ val part = tok.value
+ val afterLastNL = part.lastIndexOf('\n') + 1
+ if (afterLastNL <= 0) None
+ else {
+ val nonWs = part.indexWhere(!_.isWhitespace, afterLastNL)
+ Some((if (nonWs < 0) part.length else nonWs) - afterLastNL)
+ }
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala
index ca3c00f145..ba0aa1f73b 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/TreeOps.scala
@@ -1,7 +1,6 @@
package org.scalafmt.util
import java.{util => ju}
-import scala.collection.JavaConverters._
import scala.annotation.tailrec
import scala.collection.mutable
import scala.meta.Case
@@ -27,6 +26,8 @@ import scala.meta.tokens.Token._
import scala.meta.tokens.Tokens
import scala.reflect.ClassTag
import scala.reflect.classTag
+
+import org.scalafmt.CompatCollections.JavaConverters._
import org.scalafmt.Error
import org.scalafmt.config.{DanglingParentheses, ScalafmtConfig}
import org.scalafmt.internal.FormatToken
@@ -167,10 +168,10 @@ object TreeOps {
var stack = List.empty[Token]
tokens.foreach {
case open @ (LeftBrace() | LeftBracket() | LeftParen() |
- Interpolation.Start()) =>
+ Interpolation.Start() | Xml.Start() | Xml.SpliceStart()) =>
stack = open :: stack
case close @ (RightBrace() | RightBracket() | RightParen() |
- Interpolation.End()) =>
+ Interpolation.End() | Xml.End() | Xml.SpliceEnd()) =>
val open = stack.head
assertValidParens(open, close)
ret += hash(open) -> close
@@ -185,6 +186,8 @@ object TreeOps {
def assertValidParens(open: Token, close: Token): Unit = {
(open, close) match {
case (Interpolation.Start(), Interpolation.End()) =>
+ case (Xml.Start(), Xml.End()) =>
+ case (Xml.SpliceStart(), Xml.SpliceEnd()) =>
case (LeftBrace(), RightBrace()) =>
case (LeftBracket(), RightBracket()) =>
case (LeftParen(), RightParen()) =>
@@ -208,16 +211,8 @@ object TreeOps {
result.asScala
}
- @tailrec
- final def childOf(child: Tree, tree: Tree): Boolean = {
- child == tree || (child.parent match {
- case Some(parent) => childOf(parent, tree)
- case _ => false
- })
- }
-
- def childOf(tok: Token, tree: Tree, owners: Map[TokenHash, Tree]): Boolean =
- childOf(owners(hash(tok)), tree)
+ final def childOf(child: Tree, tree: Tree): Boolean =
+ findTreeOrParentSimple(child)(_ eq tree).isDefined
@tailrec
final def numParents(tree: Tree, cnt: Int = 0)(f: Tree => Boolean): Int =
@@ -226,6 +221,28 @@ object TreeOps {
case _ => cnt
}
+ /**
+ * Returns first ancestor which matches the given predicate.
+ */
+ @tailrec
+ def findTreeOrParent(
+ tree: Tree
+ )(pred: Tree => Option[Boolean]): Option[Tree] =
+ pred(tree) match {
+ case Some(true) => Some(tree)
+ case Some(false) => None
+ case None =>
+ tree.parent match {
+ case None => None
+ case Some(p) => findTreeOrParent(p)(pred)
+ }
+ }
+ def findTreeOrParentSimple(
+ tree: Tree,
+ flag: Boolean = true
+ )(pred: Tree => Boolean): Option[Tree] =
+ findTreeOrParent(tree)(x => if (pred(x) == flag) Some(true) else None)
+
/**
* Returns first ancestor whose parent matches the given predicate.
*/
@@ -243,9 +260,10 @@ object TreeOps {
}
}
def findTreeWithParentSimple(
- tree: Tree
+ tree: Tree,
+ flag: Boolean = true
)(pred: Tree => Boolean): Option[Tree] =
- findTreeWithParent(tree)(x => if (pred(x)) Some(true) else None)
+ findTreeWithParent(tree)(x => if (pred(x) == flag) Some(true) else None)
/**
* Returns first ancestor with a parent of a given type.
@@ -264,12 +282,6 @@ object TreeOps {
)(implicit classifier: Classifier[Tree, A]): Boolean =
findTreeWithParentOfType[A](tree).isDefined
- def isTopLevel(tree: Tree): Boolean =
- tree match {
- case _: Pkg | _: Source => true
- case _ => false
- }
-
def isDefDef(tree: Tree): Boolean =
tree match {
case _: Decl.Def | _: Defn.Def => true
@@ -414,38 +426,6 @@ object TreeOps {
splitDefnIntoParts.lift(tree)
}
- def isChainApplyParent(parent: Tree, child: Tree): Boolean =
- splitCallIntoParts.lift(parent).exists(_._1 == child)
-
- @tailrec
- final def getSelectChain(
- child: Tree,
- accum: Vector[Term.Select]
- ): Vector[Term.Select] = {
- child.parent match {
- case Some(parent: Term.Select) =>
- getSelectChain(parent, accum :+ parent)
- case Some(parent) if isChainApplyParent(parent, child) =>
- getSelectChain(parent, accum)
- case els => accum
- }
- }
-
- def startsSelectChain(tree: Tree): Boolean =
- tree match {
- case select: Term.Select =>
- !(existsChild(_.is[Term.Select])(select) &&
- existsChild(splitCallIntoParts.isDefinedAt)(select))
- case _ => false
- }
-
- /**
- * Returns true tree has a child for which f(child) is true.
- */
- def existsChild(f: Tree => Boolean)(tree: Tree): Boolean = {
- tree.children.exists(f) || tree.children.exists(existsChild(f))
- }
-
/**
* How many parents of tree are Term.Apply?
*/
@@ -457,32 +437,6 @@ object TreeOps {
def nestedSelect(tree: Tree): Int = numParents(tree)(_.is[Term.Select])
- // TODO(olafur) scala.meta should make this easier.
- def findSiblingGuard(
- generator: Enumerator.Generator
- ): Option[Enumerator.Guard] = {
- for {
- parent <- generator.parent if parent.is[Term.For] ||
- parent.is[Term.ForYield]
- sibling <- {
- val enums = parent match {
- case p: Term.For => p.enums
- case p: Term.ForYield => p.enums
- }
- val i = enums.indexOf(generator)
- if (i == -1)
- throw new IllegalStateException(
- s"Generator $generator is part of parents enums."
- )
- enums
- .drop(i + 1)
- .takeWhile(_.is[Enumerator.Guard])
- .lastOption
- .asInstanceOf[Option[Enumerator.Guard]]
- }
- } yield sibling
- }
-
/**
* Calculates depth to deepest child in tree.
*/
@@ -688,14 +642,15 @@ object TreeOps {
}
}
- def isTripleQuote(token: Token): Boolean = token.syntax.startsWith("\"\"\"")
+ @inline
+ def isTripleQuote(syntax: String): Boolean = syntax.startsWith("\"\"\"")
def getStripMarginChar(ft: FormatToken): Option[Char] = {
ft.left match {
case _: Token.Interpolation.Start =>
val ti = TreeOps.findInterpolate(ft.meta.leftOwner)
ti.flatMap(TreeOps.getStripMarginChar)
- case string: Token.Constant.String if isTripleQuote(string) =>
+ case _: Token.Constant.String if isTripleQuote(ft.meta.left.text) =>
TreeOps.getStripMarginChar(ft.meta.leftOwner)
case _ => None
}
@@ -719,4 +674,15 @@ object TreeOps {
}
}
+ @inline
+ def ifWithoutElse(t: Term.If) = t.elsep.is[Lit.Unit]
+
+ def cannotStartSelectChain(select: Term.Select): Boolean =
+ select.qual match {
+ case _: Term.Placeholder => true
+ case t: Term.Name => isSymbolicName(t.value)
+ case t: Term.Select => isSymbolicName(t.name.value)
+ case _ => false
+ }
+
}
diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/ValidationOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/ValidationOps.scala
index 4a7eb30d05..55795e7dc1 100644
--- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/ValidationOps.scala
+++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/ValidationOps.scala
@@ -3,19 +3,25 @@ package org.scalafmt.util
import scala.collection.mutable
object ValidationOps {
- def assertNonNegative(ns: sourcecode.Text[Int]*): Unit = {
+
+ def addIfNegative(
+ ns: sourcecode.Text[Int]*
+ )(implicit errors: mutable.Buffer[String]): Unit =
ns.foreach { n =>
if (n.value < 0)
- throw new IllegalArgumentException(
- s"${n.source} must be non-negative, was ${n.value}"
- )
+ errors += s"${n.source} must be non-negative, was ${n.value}"
}
- }
def addIf(
what: sourcecode.Text[Boolean],
why: => String = ""
)(implicit errors: mutable.Buffer[String]): Unit =
- if (what.value) errors += what.source + why
+ addIfDirect(what.value, what.source + why)
+
+ def addIfDirect(
+ what: Boolean,
+ why: => String = ""
+ )(implicit errors: mutable.Buffer[String]): Unit =
+ if (what) errors += why
}
diff --git a/scalafmt-docs/src/main/scala/docs/DefaultsModifier.scala b/scalafmt-docs/src/main/scala/docs/DefaultsModifier.scala
index 98dc55add5..35da161292 100644
--- a/scalafmt-docs/src/main/scala/docs/DefaultsModifier.scala
+++ b/scalafmt-docs/src/main/scala/docs/DefaultsModifier.scala
@@ -24,14 +24,29 @@ class DefaultsModifier extends StringModifier {
Conf.printHocon(ScalafmtConfig.default)
"```\n" + result + "\n```"
} else {
- val path = code.text.split("\\.").toList
- val down = path.foldLeft(default.dynamic)(_ selectDynamic _)
- down.asConf match {
- case Configured.Ok(value) =>
- "Default: `" + code.text.trim + " = " + value.toString + "`\n"
- case Configured.NotOk(e) =>
- reporter.error(Position.Range(code, 0, 0), e.toString())
- "fail"
+ def getDefaultValue(param: String): String = {
+ val path = param.split("\\.").toList
+ val down = path.foldLeft(default.dynamic)(_ selectDynamic _)
+ down.asConf match {
+ case Configured.Ok(value) =>
+ value.toString()
+ case Configured.NotOk(e) =>
+ reporter.error(Position.Range(code, 0, 0), e.toString())
+ ""
+ }
+ }
+
+ val params = code.text.split("\\s+")
+ if (params.length == 1) {
+ val param = params(0)
+ s"Default: `$param = ${getDefaultValue(param)}`\n"
+ } else {
+ val defaults = params.map { param =>
+ s"$param = ${getDefaultValue(param)}"
+ }
+ ScalafmtModifier.mdConfigCodeBlock(
+ defaults.mkString("# Defaults\n", "\n", "")
+ )
}
}
}
diff --git a/scalafmt-docs/src/main/scala/docs/ScalafmtModifier.scala b/scalafmt-docs/src/main/scala/docs/ScalafmtModifier.scala
index 7750cfc930..6490169e5c 100644
--- a/scalafmt-docs/src/main/scala/docs/ScalafmtModifier.scala
+++ b/scalafmt-docs/src/main/scala/docs/ScalafmtModifier.scala
@@ -12,6 +12,11 @@ import mdoc.Reporter
import mdoc.StringModifier
class ScalafmtModifier extends StringModifier {
+
+ import ScalafmtModifier._
+
+ private final val separator = "---\n"
+
override val name: String = "scalafmt"
override def process(
info: String,
@@ -22,32 +27,27 @@ class ScalafmtModifier extends StringModifier {
if (code.text.contains("package")) ScalafmtRunner.default
else ScalafmtRunner.sbt
val base = ScalafmtConfig.default.copy(runner = runner, maxColumn = 40)
- val i = code.text.indexOf("---")
+ val i = code.text.indexOf(separator)
val pos = Position.Range(code, 0, 0)
if (i == -1) {
reporter.error(pos, "Missing ---")
"fail"
} else {
val config = Input.Slice(code, 0, i)
- val program = Input.Slice(code, i + 3, code.chars.length)
+ val program = Input.Slice(code, i + separator.length, code.chars.length)
Config.fromHoconString(config.text, None, base) match {
case Configured.Ok(c) =>
- val parsedConfig = c.copy(runner = runner)
- Scalafmt.format(program.text, parsedConfig).toEither match {
+ Scalafmt.format(program.text, c).toEither match {
case Right(formatted) =>
val configText = config.text.trim
val configBlock =
if (configText == "") ""
- else
- mdConfigSection(
- "Config for this example",
- mdCodeBlock("scala config", configText)
- )
+ else mdConfigSection("Config for this example:", configText)
val formattedCodeBlock =
- mdCodeBlock("scala formatted", formatted.trim)
+ mdScalaCodeBlock("formatted", formatted.trim)
val originalCodeBlock =
- mdCodeBlock("scala original", program.text.trim)
+ mdScalaCodeBlock("original", program.text.trim)
List(
formattedCodeBlock,
originalCodeBlock,
@@ -67,10 +67,22 @@ class ScalafmtModifier extends StringModifier {
}
}
- private def mdCodeBlock(language: String, content: String): String =
+}
+
+object ScalafmtModifier {
+
+ def mdCodeBlock(language: String, content: String): String =
s"```$language\n$content\n```"
- private def mdConfigSection(title: String, content: String): String =
+ def mdScalaCodeBlock(style: String, content: String): String =
+ mdCodeBlock(s"scala $style", content)
+
+ def mdConfigCodeBlock(content: String): String =
+ mdScalaCodeBlock("config", content)
+
+ def mdConfigSection(title: String, code: String): String = {
+ val content = mdConfigCodeBlock(code)
s"$title
\n$content\n
\n"
+ }
}
diff --git a/scalafmt-docs/src/main/scala/website/package.scala b/scalafmt-docs/src/main/scala/website/package.scala
index c8ac599804..e067432de4 100644
--- a/scalafmt-docs/src/main/scala/website/package.scala
+++ b/scalafmt-docs/src/main/scala/website/package.scala
@@ -11,8 +11,7 @@ package object website {
def replaceMargin(s: String): String = {
val buf = new StringBuilder
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- for (line <- augmentString(s).lines) {
+ for (line <- s.linesIterator) {
val len = line.length
var index = 0
while (index < len && line.charAt(index) <= ' ') {
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala
index 5632ba35d5..f4e92500fd 100644
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala
+++ b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala
@@ -8,13 +8,13 @@ import com.typesafe.config.{ConfigException, ConfigFactory}
import coursierapi.{MavenRepository, Repository}
import org.scalafmt.dynamic.ScalafmtDynamic.{FormatEval, FormatResult}
import org.scalafmt.dynamic.ScalafmtDynamicDownloader._
+import org.scalafmt.dynamic.ScalafmtDynamicError._
import org.scalafmt.dynamic.exceptions._
import org.scalafmt.dynamic.utils.ReentrantCache
import org.scalafmt.interfaces._
import scala.concurrent.ExecutionContext
import scala.util.Try
-import scala.util.control.NonFatal
final case class ScalafmtDynamic(
reporter: ScalafmtReporter,
@@ -22,7 +22,7 @@ final case class ScalafmtDynamic(
respectVersion: Boolean,
respectExcludeFilters: Boolean,
defaultVersion: String,
- formatCache: ReentrantCache[String, FormatEval[ScalafmtReflect]],
+ formatCache: ReentrantCache[ScalafmtVersion, FormatEval[ScalafmtReflect]],
cacheConfigs: Boolean,
configsCache: ReentrantCache[Path, FormatEval[
(ScalafmtReflectConfig, FileTime)
@@ -74,28 +74,20 @@ final case class ScalafmtDynamic(
codeFormatted
case Left(error) =>
reportError(file, error)
+ if (error.isInstanceOf[ConfigError]) throw error
code
}
}
private def reportError(file: Path, error: ScalafmtDynamicError): Unit = {
- import ScalafmtDynamicError._
error match {
- case ConfigParseError(configPath, cause) =>
- reporter.error(configPath, cause.getMessage)
- case ConfigDoesNotExist(configPath) =>
- reporter.error(configPath, "file does not exist")
- case ConfigMissingVersion(configPath) =>
- reporter.missingVersion(configPath, defaultVersion)
- case CannotDownload(configPath, version, cause) =>
- val message = s"failed to resolve Scalafmt version '$version'"
- cause match {
- case Some(e) => reporter.error(configPath, message, e)
- case None => reporter.error(configPath, message)
+ case e: ConfigMissingVersion =>
+ reporter.missingVersion(e.configPath, defaultVersion)
+ case e: ConfigError =>
+ Option(e.getCause) match {
+ case Some(cause) => reporter.error(e.configPath, e.getMessage, cause)
+ case None => reporter.error(e.configPath, e.getMessage)
}
- case CorruptedClassPath(configPath, version, _, cause) =>
- val message = s"scalafmt version $version classpath is corrupted"
- reporter.error(configPath, message, cause)
case UnknownError(cause) =>
reporter.error(file, cause)
}
@@ -108,7 +100,7 @@ final case class ScalafmtDynamic(
): FormatResult = {
for {
config <- resolveConfig(configPath)
- codeFormatted <- tryFormat(file, code, config.fmtReflect, config)
+ codeFormatted <- tryFormat(file, code, config)
} yield codeFormatted
}
@@ -116,7 +108,7 @@ final case class ScalafmtDynamic(
configPath: Path
): Either[ScalafmtDynamicError, ScalafmtReflectConfig] = {
if (!Files.exists(configPath)) {
- Left(ScalafmtDynamicError.ConfigDoesNotExist(configPath))
+ Left(new ConfigDoesNotExist(configPath))
} else if (cacheConfigs) {
val currentTimestamp: FileTime = Files.getLastModifiedTime(configPath)
configsCache
@@ -138,9 +130,7 @@ final case class ScalafmtDynamic(
configPath: Path
): FormatEval[ScalafmtReflectConfig] = {
for {
- version <- readVersion(configPath).toRight(
- ScalafmtDynamicError.ConfigMissingVersion(configPath)
- )
+ version <- readVersion(configPath)
fmtReflect <- resolveFormatter(configPath, version)
config <- parseConfig(configPath, fmtReflect)
} yield config
@@ -151,37 +141,31 @@ final case class ScalafmtDynamic(
fmtReflect: ScalafmtReflect
): FormatEval[ScalafmtReflectConfig] = {
Try(fmtReflect.parseConfig(configPath)).toEither.left.map {
- case ex: ScalafmtConfigException =>
- ScalafmtDynamicError.ConfigParseError(configPath, ex)
- case ex =>
- ScalafmtDynamicError.UnknownError(ex)
+ case ex: ScalafmtDynamicError => ex
+ case ex => UnknownError(ex)
}
}
private def resolveFormatter(
configPath: Path,
- version: String
+ version: ScalafmtVersion
): FormatEval[ScalafmtReflect] = {
formatCache.getOrAddToCache(version) { () =>
val writer = reporter.downloadOutputStreamWriter()
- val downloader = new ScalafmtDynamicDownloader(writer, repositories)
- downloader
+ new ScalafmtDynamicDownloader(writer, repositories)
.download(version)
.left
.map {
- case DownloadResolutionError(v, _) =>
- ScalafmtDynamicError.CannotDownload(configPath, v, None)
- case DownloadUnknownError(v, cause) =>
- ScalafmtDynamicError.CannotDownload(configPath, v, Option(cause))
- case InvalidVersionError(v, cause) =>
- ScalafmtDynamicError.CannotDownload(configPath, v, Option(cause))
+ case _: DownloadResolutionError =>
+ new CannotDownload(configPath, version)
+ case DownloadUnknownError(cause) =>
+ new CannotDownload(configPath, version, cause)
}
- .flatMap(resolveClassPath(configPath, _))
+ .flatMap(resolveClassPath(configPath))
}
}
- private def resolveClassPath(
- configPath: Path,
+ private def resolveClassPath(configPath: Path)(
downloadSuccess: DownloadSuccess
): FormatEval[ScalafmtReflect] = {
val DownloadSuccess(version, urls) = downloadSuccess
@@ -190,22 +174,24 @@ final case class ScalafmtDynamic(
ScalafmtReflect(classloader, version, respectVersion)
}.toEither.left.map {
case e: ReflectiveOperationException =>
- ScalafmtDynamicError.CorruptedClassPath(configPath, version, urls, e)
+ new CorruptedClassPath(configPath, version, urls, e)
case e =>
- ScalafmtDynamicError.UnknownError(e)
+ UnknownError(e)
}
}
private def tryFormat(
file: Path,
code: String,
- reflect: ScalafmtReflect,
config: ScalafmtReflectConfig
): FormatResult = {
Try {
val filename = file.toString
val configWithDialect: ScalafmtReflectConfig =
- if (filename.endsWith(".sbt") || filename.endsWith(".sc")) {
+ if (
+ config.fmtReflect.version < ScalafmtVersion(2, 6, 3) &&
+ (filename.endsWith(".sbt") || filename.endsWith(".sc"))
+ ) {
config.withSbtDialect
} else {
config
@@ -214,11 +200,11 @@ final case class ScalafmtDynamic(
reporter.excluded(file)
code
} else {
- reflect.format(code, configWithDialect, Some(file))
+ configWithDialect.format(code, Some(file))
}
}.toEither.left.map {
- case ReflectionException(e) => ScalafmtDynamicError.UnknownError(e)
- case e => ScalafmtDynamicError.UnknownError(e)
+ case ReflectionException(e) => UnknownError(e)
+ case e => UnknownError(e)
}
}
@@ -229,15 +215,20 @@ final case class ScalafmtDynamic(
respectExcludeFilters && !config.isIncludedInProject(filename)
}
- private def readVersion(config: Path): Option[String] = {
- try {
- Some(ConfigFactory.parseFile(config.toFile).getString("version"))
- } catch {
- case _: ConfigException.Missing if !respectVersion =>
- Some(defaultVersion)
- case NonFatal(_) =>
- None
- }
+ private def readVersion(config: Path): FormatEval[ScalafmtVersion] = {
+ Try {
+ ConfigFactory.parseFile(config.toFile).getString("version")
+ }.toEither.left
+ .flatMap {
+ case e: ConfigException.Parse =>
+ Left(new ConfigParseError(config, e.getMessage))
+ case _: ConfigException.Missing =>
+ if (respectVersion) Left(new ConfigMissingVersion(config))
+ else Right(defaultVersion)
+ }
+ .flatMap { v =>
+ ScalafmtVersion.parse(v).toRight(new ConfigInvalidVersion(config, v))
+ }
}
override def withMavenRepositories(repositories: String*): Scalafmt =
@@ -245,13 +236,13 @@ final case class ScalafmtDynamic(
override def createSession(config: Path): ScalafmtSession =
resolveConfig(config).fold(
- error => { reportError(config, error); null },
+ error => { reportError(config, error); throw error },
config => new MySession(config)
)
private class MySession(cfg: ScalafmtReflectConfig) extends ScalafmtSession {
override def format(file: Path, code: String): String =
- tryFormat(file, code, cfg.fmtReflect, cfg).fold(
+ tryFormat(file, code, cfg).fold(
error => { reportError(file, error); code },
formatted => formatted
)
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala
index b79ca9935e..9e61c03ea8 100644
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala
+++ b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala
@@ -4,8 +4,6 @@ import java.io.OutputStreamWriter
import java.net.URL
import coursierapi._
-import org.scalafmt.dynamic.ScalafmtDynamicDownloader._
-import org.scalafmt.dynamic.ScalafmtVersion.InvalidVersionException
import scala.collection.JavaConverters._
import scala.concurrent.duration.Duration
@@ -16,13 +14,7 @@ class ScalafmtDynamicDownloader(
customRepositories: List[Repository],
ttl: Option[Duration] = None
) {
-
- def download(version: String): Either[DownloadFailure, DownloadSuccess] =
- ScalafmtVersion
- .parse(version)
- .left
- .map(InvalidVersionError(version, _))
- .flatMap(download)
+ import ScalafmtDynamicDownloader._
def download(
version: ScalafmtVersion
@@ -42,12 +34,12 @@ class ScalafmtDynamicDownloader(
.withCache(Cache.create())
val urls: Array[URL] =
settings.fetch().asScala.iterator.map(_.toURI.toURL).toArray
- DownloadSuccess(version.toString, urls)
+ DownloadSuccess(version, urls)
}.toEither.left.map {
case e: error.ResolutionError =>
- DownloadResolutionError(version.toString, e)
+ DownloadResolutionError(e)
case e =>
- DownloadUnknownError(version.toString, e)
+ DownloadUnknownError(e)
}
}
@@ -100,22 +92,12 @@ class ScalafmtDynamicDownloader(
}
object ScalafmtDynamicDownloader {
- sealed trait DownloadResult {
- def version: String
- }
- case class DownloadSuccess(version: String, jarUrls: Seq[URL])
- extends DownloadResult
- sealed trait DownloadFailure extends DownloadResult {
+ case class DownloadSuccess(version: ScalafmtVersion, jarUrls: Seq[URL])
+
+ sealed trait DownloadFailure {
def cause: Throwable
}
- case class DownloadResolutionError(
- version: String,
- cause: error.ResolutionError
- ) extends DownloadFailure
- case class DownloadUnknownError(version: String, cause: Throwable)
+ case class DownloadResolutionError(cause: error.ResolutionError)
extends DownloadFailure
- case class InvalidVersionError(
- version: String,
- cause: InvalidVersionException
- ) extends DownloadFailure
+ case class DownloadUnknownError(cause: Throwable) extends DownloadFailure
}
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala
index d02cca7a3a..04bf2eb825 100644
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala
+++ b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala
@@ -3,30 +3,46 @@ package org.scalafmt.dynamic
import java.net.URL
import java.nio.file.Path
-import org.scalafmt.dynamic.exceptions.ScalafmtConfigException
+import scala.util.control.NoStackTrace
-sealed trait ScalafmtDynamicError
+sealed abstract class ScalafmtDynamicError(
+ msg: String,
+ cause: Throwable = null
+) extends Error(msg, cause)
object ScalafmtDynamicError {
- case class ConfigDoesNotExist(configPath: Path) extends ScalafmtDynamicError
+ sealed abstract class ConfigError(
+ val configPath: Path,
+ msg: String,
+ cause: Throwable = null
+ ) extends ScalafmtDynamicError(msg, cause)
- case class ConfigMissingVersion(configPath: Path) extends ScalafmtDynamicError
+ class ConfigDoesNotExist(configPath: Path)
+ extends ConfigError(configPath, "Missing config")
- case class ConfigParseError(configPath: Path, cause: ScalafmtConfigException)
- extends ScalafmtDynamicError
+ class ConfigMissingVersion(configPath: Path)
+ extends ConfigError(configPath, "Missing version")
- case class CannotDownload(
+ class ConfigParseError(configPath: Path, why: String)
+ extends ConfigError(configPath, s"Invalid config: $why")
+
+ class CannotDownload(
configPath: Path,
- version: String,
- cause: Option[Throwable]
- ) extends ScalafmtDynamicError
+ version: ScalafmtVersion,
+ cause: Throwable = null
+ ) extends ConfigError(configPath, s"failed to download v=$version", cause)
- case class CorruptedClassPath(
+ class CorruptedClassPath(
configPath: Path,
- version: String,
- urls: Seq[URL],
+ version: ScalafmtVersion,
+ val urls: Seq[URL],
cause: Throwable
- ) extends ScalafmtDynamicError
+ ) extends ConfigError(configPath, s"corrupted class path v=$version", cause)
+
+ class ConfigInvalidVersion(configPath: Path, version: String)
+ extends ConfigError(configPath, s"Invalid version: $version")
+ with NoStackTrace
- case class UnknownError(cause: Throwable) extends ScalafmtDynamicError
+ case class UnknownError(cause: Throwable)
+ extends ScalafmtDynamicError("unknown error", cause)
}
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala
index 9d6634279a..49de3a28a9 100644
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala
+++ b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala
@@ -9,7 +9,7 @@ import scala.util.Try
case class ScalafmtReflect(
classLoader: URLClassLoader,
- version: String,
+ version: ScalafmtVersion,
respectVersion: Boolean
) {
import classLoader.loadClass
@@ -50,7 +50,7 @@ case class ScalafmtReflect(
// TODO: see implementation details for other versions of scalafmt, find where intellij config is kept
lazy val intellijScalaFmtConfig: Option[ScalafmtReflectConfig] = {
- if (version == "1.5.1") {
+ if (version == ScalafmtVersion(1, 5, 1)) {
val scalaFmtConfigCls =
classLoader.loadClass("org.scalafmt.config.ScalafmtConfig")
val configTarget = scalaFmtConfigCls.invokeStatic("intellij")
@@ -62,10 +62,6 @@ case class ScalafmtReflect(
def parseConfig(configPath: Path): ScalafmtReflectConfig = {
val configText = ConfigFactory.parseFile(configPath.toFile).root.render()
- parseConfigFromString(configText)
- }
-
- def parseConfigFromString(configText: String): ScalafmtReflectConfig = {
val configured: Object =
try { // scalafmt >= 1.6.0
scalafmtCls.invokeStatic("parseHoconConfig", configText.asParam)
@@ -79,13 +75,21 @@ case class ScalafmtReflect(
configText.asParam,
fromHoconEmptyPath.asParam(optionCls)
)
+ case ReflectionException(e) =>
+ throw new ScalafmtDynamicError.ConfigParseError(
+ configPath,
+ e.getMessage
+ )
}
try {
new ScalafmtReflectConfig(this, configured.invoke("get"), classLoader)
} catch {
case ReflectionException(e) =>
- throw ScalafmtConfigException(e.getMessage)
+ throw new ScalafmtDynamicError.ConfigParseError(
+ configPath,
+ e.getMessage
+ )
}
}
@@ -94,6 +98,7 @@ case class ScalafmtReflect(
config: ScalafmtReflectConfig,
fileOpt: Option[Path] = None
): String = {
+ require(this eq config.fmtReflect)
checkVersionMismatch(config)
val formatted = (formatMethodWithFilename, fileOpt) match {
@@ -158,9 +163,10 @@ case class ScalafmtReflect(
private def checkVersionMismatch(config: ScalafmtReflectConfig): Unit = {
if (respectVersion) {
+ val expected = version.toString
val obtained = config.version
- if (obtained != version) {
- throw VersionMismatch(obtained, version)
+ if (obtained != expected) {
+ throw VersionMismatch(obtained, expected)
}
}
}
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala
index e8eb03edda..9cc3888aaa 100644
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala
+++ b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala
@@ -1,6 +1,7 @@
package org.scalafmt.dynamic
import java.lang.reflect.Constructor
+import java.nio.file.Path
import org.scalafmt.dynamic.exceptions.ReflectionException
import org.scalafmt.dynamic.utils.ReflectUtils._
@@ -44,7 +45,7 @@ class ScalafmtReflectConfig private[dynamic] (
matcher.invokeAs[java.lang.Boolean]("matches", filename.asParam)
}
- private val sbtDialect: Object = {
+ private def sbtDialect: Object = {
try dialectsCls.invokeStatic("Sbt")
catch {
case ReflectionException(_: NoSuchMethodException) =>
@@ -52,9 +53,13 @@ class ScalafmtReflectConfig private[dynamic] (
}
}
- def withSbtDialect: ScalafmtReflectConfig = {
- // TODO: maybe hold loaded classes in some helper class not to reload them each time during copy?
- val newTarget = target.invoke("withDialect", (dialectCls, sbtDialect))
+ lazy val withSbtDialect: ScalafmtReflectConfig = {
+ val newTarget =
+ try target.invoke("forSbt")
+ catch {
+ case ReflectionException(_: NoSuchMethodException) =>
+ target.invoke("withDialect", (dialectCls, sbtDialect))
+ }
new ScalafmtReflectConfig(fmtReflect, newTarget, classLoader)
}
@@ -112,6 +117,9 @@ class ScalafmtReflectConfig private[dynamic] (
}
}
+ def format(code: String, file: Option[Path]): String =
+ fmtReflect.format(code, this, file)
+
override def equals(obj: Any): Boolean = target.equals(obj)
override def hashCode(): Int = target.hashCode()
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala
index 5b22e4033e..06457b7dc4 100644
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala
+++ b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala
@@ -1,12 +1,12 @@
package org.scalafmt.dynamic
-import scala.util.control.{NonFatal, NoStackTrace}
+import scala.util.control.NonFatal
case class ScalafmtVersion(
major: Int,
minor: Int,
patch: Int,
- rc: Int,
+ rc: Int = 0,
snapshot: Boolean = false
) {
private val integerRepr: Int =
@@ -17,7 +17,7 @@ case class ScalafmtVersion(
rc != 0 && (other.rc == 0 || rc < other.rc)
else integerRepr < other.integerRepr
- def >(other: ScalafmtVersion): Boolean = this != other && !(this < other)
+ def >(other: ScalafmtVersion): Boolean = other < this
override def toString: String =
s"$major.$minor.$patch" +
@@ -26,17 +26,14 @@ case class ScalafmtVersion(
}
object ScalafmtVersion {
- case class InvalidVersionException(version: String)
- extends Exception(s"Invalid scalafmt version $version")
- with NoStackTrace
private val versionRegex = """(\d)\.(\d)\.(\d)(-RC(\d))?(-SNAPSHOT)?""".r
- def parse(version: String): Either[InvalidVersionException, ScalafmtVersion] =
+ def parse(version: String): Option[ScalafmtVersion] =
try {
version match {
case versionRegex(major, minor, patch, null, null, snapshot) =>
- Right(
+ Some(
ScalafmtVersion(
positiveInt(major),
positiveInt(minor),
@@ -46,7 +43,7 @@ object ScalafmtVersion {
)
)
case versionRegex(major, minor, patch, _, rc, snapshot) =>
- Right(
+ Some(
ScalafmtVersion(
positiveInt(major),
positiveInt(minor),
@@ -55,10 +52,10 @@ object ScalafmtVersion {
snapshot != null
)
)
- case _ => Left(InvalidVersionException(version))
+ case _ => None
}
} catch {
- case e if NonFatal(e) => Left(InvalidVersionException(version))
+ case e if NonFatal(e) => None
}
private def positiveInt(s: String): Int = {
diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtConfigException.scala b/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtConfigException.scala
deleted file mode 100644
index a888d05c43..0000000000
--- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtConfigException.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package org.scalafmt.dynamic.exceptions
-
-case class ScalafmtConfigException(e: String) extends Exception(e)
diff --git a/scalafmt-tests/src/main/scala/org/scalafmt/util/DiffAssertions.scala b/scalafmt-tests/src/main/scala/org/scalafmt/util/DiffAssertions.scala
index 34d8c72363..8094fe6395 100644
--- a/scalafmt-tests/src/main/scala/org/scalafmt/util/DiffAssertions.scala
+++ b/scalafmt-tests/src/main/scala/org/scalafmt/util/DiffAssertions.scala
@@ -94,7 +94,7 @@ trait DiffAssertions extends AnyFunSuiteLike {
compareContents(original.trim.split("\n"), revised.trim.split("\n"))
def compareContents(original: Seq[String], revised: Seq[String]): String = {
- import collection.JavaConverters._
+ import org.scalafmt.CompatCollections.JavaConverters._
val diff = difflib.DiffUtils.diff(original.asJava, revised.asJava)
if (diff.getDeltas.isEmpty) ""
else
diff --git a/scalafmt-tests/src/test/resources/align/ArrayIndexOutOfBounds800.source b/scalafmt-tests/src/test/resources/align/ArrayIndexOutOfBounds800.source
deleted file mode 100644
index 78b2f408cf..0000000000
--- a/scalafmt-tests/src/test/resources/align/ArrayIndexOutOfBounds800.source
+++ /dev/null
@@ -1,26 +0,0 @@
-preset = defaultWithAlign
-edition = 2019-10
-onTestFailure = "Search state"
-align.preset = most
-danglingParentheses.preset = false
-<<< #800
-object Test {
- Some(Files.newInputStream(inFile)) match {
- case Some(reader) ⇒
- val srcStream: Source[Int, NotUsed] =
- Source.single((3, 4)).flatMapConcat {
- case ((n1, n2)) ⇒
- Source.fromGraph(GraphDSL.create() { implicit b ⇒
- val src = Source.fromIterator(
- () ⇒
- NodeStreamer.makeIterator(
- (n, p) ⇒ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- ))
- val flow = src ~> procStream
- SourceShape(flow.outlet)
- })
- }
- }
-}
->>>
-object Test
diff --git a/scalafmt-tests/src/test/resources/align/ArrowEnumeratorGenerator.stat b/scalafmt-tests/src/test/resources/align/ArrowEnumeratorGenerator.stat
index 1d25c812f3..80d684dae9 100644
--- a/scalafmt-tests/src/test/resources/align/ArrowEnumeratorGenerator.stat
+++ b/scalafmt-tests/src/test/resources/align/ArrowEnumeratorGenerator.stat
@@ -24,8 +24,9 @@ for {
} yield cond
>>>
for {
- variable <- record.field.field1
- .map(_.toString)
+ variable <- record.field.field1.map(
+ _.toString
+ )
cond <- if (variable) doSomething
else doAnything
} yield cond
@@ -36,8 +37,9 @@ for {
} yield cond
>>>
for {
- variable <- record.field.field1
- .map(_.toString)
+ variable <- record.field.field1.map(
+ _.toString
+ )
cond <- if (variable) doSomething
else doAnything
} yield cond
@@ -58,8 +60,9 @@ for {
} yield cond
>>>
for {
- variable <- record.field.field1
- .map(_.toString)
+ variable <- record.field.field1.map(
+ _.toString
+ )
cond = if (variable) doSomething
else doAnything
} yield cond
diff --git a/scalafmt-tests/src/test/resources/align/BeforeCommentWithinMethodChain.stat b/scalafmt-tests/src/test/resources/align/BeforeCommentWithinMethodChain.stat
index 073d5f8a7c..61008d0d65 100644
--- a/scalafmt-tests/src/test/resources/align/BeforeCommentWithinMethodChain.stat
+++ b/scalafmt-tests/src/test/resources/align/BeforeCommentWithinMethodChain.stat
@@ -1,8 +1,8 @@
maxColumn = 40
<<< keep indentation for comments within method chain in statement
foo()
-// bar
-// baz
+ // bar
+ // baz
.bar()
>>>
foo()
@@ -11,8 +11,8 @@ foo()
.bar()
<<< keep indentation for comments within method chain in assignment
val res = foo()
-// bar
-// baz
+ // bar
+ // baz
.bar()
>>>
val res = foo()
@@ -21,11 +21,11 @@ val res = foo()
.bar()
<<< don't add extra indentation for comments in middle of chain
"x"
-// bar
-// baz
+ // bar
+ // baz
.bar()
-// bar2
-// baz2
+ // bar2
+ // baz2
.bar2()
>>>
"x"
diff --git a/scalafmt-tests/src/test/resources/default/Apply.stat b/scalafmt-tests/src/test/resources/default/Apply.stat
index 14ad98acab..90f4ccbbeb 100644
--- a/scalafmt-tests/src/test/resources/default/Apply.stat
+++ b/scalafmt-tests/src/test/resources/default/Apply.stat
@@ -525,6 +525,8 @@ def startSharding(system: ActorSystem,
{ case (name: String, _) => (name.hashCode % shardCount).toString }
)
<<< spray shit
+newlines.avoidForSimpleOverflow = [punct]
+===
{{{
get {
respondWithMediaType(MediaTypes.`application/json`) {
@@ -562,19 +564,19 @@ def startSharding(system: ActorSystem,
"plugins" -> Map(
"outputblockers" -> pluginContext.outputBlockers.map {
case (n, p) =>
- n -> Map("name" -> p.pluginName,
- "description" -> p.pluginDescription,
- "class" -> p.getClass.getName,
- "params" -> pluginContext.pluginParams(
- p.pluginName))
+ n -> Map(
+ "name" -> p.pluginName,
+ "description" -> p.pluginDescription,
+ "class" -> p.getClass.getName,
+ "params" -> pluginContext.pluginParams(p.pluginName))
},
"outputsniffers" -> pluginContext.outputSniffers.map {
case (n, p) =>
- n -> Map("name" -> p.pluginName,
- "description" -> p.pluginDescription,
- "class" -> p.getClass.getName,
- "params" -> pluginContext.pluginParams(
- p.pluginName))
+ n -> Map(
+ "name" -> p.pluginName,
+ "description" -> p.pluginDescription,
+ "class" -> p.getClass.getName,
+ "params" -> pluginContext.pluginParams(p.pluginName))
}
))
}
@@ -584,6 +586,8 @@ def startSharding(system: ActorSystem,
}
}
<<< nested apply with assign
+newlines.avoidForSimpleOverflow = [punct]
+===
opt[String]("json-extractor") action { (x, c) =>
c.copy( common =
c.common.copy(
@@ -591,8 +595,7 @@ opt[String]("json-extractor") action { (x, c) =>
}
>>>
opt[String]("json-extractor") action { (x, c) =>
- c.copy(common =
- c.common.copy(jsonExtractor = JsonExtractorOption.withName(x)))
+ c.copy(common = c.common.copy(jsonExtractor = JsonExtractorOption.withName(x)))
}
<<< nested apply with assign 2
{
@@ -635,9 +638,12 @@ SSLConfig(loose = SSLLooseConfig(allowLegacyHelloMessages = None)/*comment 123 c
}
>>>
{
- val config =
- WSClientConfig(SSLConfig(loose = SSLLooseConfig(allowLegacyHelloMessages =
- None) /*comment 123 comment 234*/ )) //comment 345 comment 456
+ val config = WSClientConfig(
+ SSLConfig(loose =
+ SSLLooseConfig(allowLegacyHelloMessages =
+ None) /*comment 123 comment 234*/
+ )
+ ) //comment 345 comment 456
}
<<< #1604 3: apply with assign and attached comments
{
@@ -696,6 +702,8 @@ val a = new b( /*c1*/ c =>
/*c2*/ e => f,
g => h)
<<< def with multiple curried single parameters
+newlines.avoidForSimpleOverflow = [punct]
+===
object ThreadPoolConfigDispatcherBuilder {
def conf_?[T](opt: Option[T])(
fun: (
@@ -706,8 +714,8 @@ val a = new b( /*c1*/ c =>
}
>>>
object ThreadPoolConfigDispatcherBuilder {
- def conf_?[T](opt: Option[T])(fun: (
- T) => ThreadPoolConfigDispatcherBuilder => ThreadPoolConfigDispatcherBuilder)
+ def conf_?[T](opt: Option[T])(
+ fun: (T) => ThreadPoolConfigDispatcherBuilder => ThreadPoolConfigDispatcherBuilder)
: Option[(ThreadPoolConfigDispatcherBuilder) => ThreadPoolConfigDispatcherBuilder] =
opt map fun
}
@@ -753,3 +761,626 @@ object a {
// c1
)
}
+<<< #1798
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+===
+ commonConfig(
+ debugConfig(on = false)
+ .withFallback(ConfigFactory.parseString("""
+ akka.cluster.periodic-tasks-initial-delay = 300 s # turn off all periodic tasks
+ akka.cluster.publish-stats-interval = 0 s # always, when it happens
+ """))
+ .withFallback(
+ MultiNodeClusterSpec.clusterConfigWithFailureDetectorPuppet))
+>>>
+commonConfig(
+ debugConfig(on = false)
+ .withFallback(ConfigFactory.parseString("""
+ akka.cluster.periodic-tasks-initial-delay = 300 s # turn off all periodic tasks
+ akka.cluster.publish-stats-interval = 0 s # always, when it happens
+ """))
+ .withFallback(MultiNodeClusterSpec.clusterConfigWithFailureDetectorPuppet)
+)
+<<< #1799
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+===
+override final def next(): To =
+ if (hasNext) {
+ val ret = _next
+ _next = null.asInstanceOf[To] // Mark as consumed aaaaaaaaaaaaaaaaaa(nice to the GC, don't leak the last returned value)
+ _hasNext = false // Mark as consumed (we need to look for the next value)
+ ret
+ } else throw new java.util.NoSuchElementException("next")
+>>>
+override final def next(): To =
+ if (hasNext) {
+ val ret = _next
+ _next =
+ null.asInstanceOf[To] // Mark as consumed aaaaaaaaaaaaaaaaaa(nice to the GC, don't leak the last returned value)
+ _hasNext = false // Mark as consumed (we need to look for the next value)
+ ret
+ } else throw new java.util.NoSuchElementException("next")
+<<< #1800
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+===
+final case class StreamedEntityCreator[
+ -A <: ParserOutput, +B >: HttpEntity.Strict <: HttpEntity](
+ creator: Source[A, NotUsed] ⇒ B)
+ extends EntityCreator[A, B] {
+ def apply(parts: Source[A, NotUsed]) = creator(parts)
+}
+>>>
+final case class StreamedEntityCreator[
+ -A <: ParserOutput,
+ +B >: HttpEntity.Strict <: HttpEntity
+](creator: Source[A, NotUsed] ⇒ B)
+ extends EntityCreator[A, B] {
+ def apply(parts: Source[A, NotUsed]) = creator(parts)
+}
+<<< #1801
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+newlines.avoidForSimpleOverflow = [tooLong]
+===
+pushInput(closeFrame(
+ Protocol.CloseCodes.UnexpectedCondition,
+ mask = true,
+ msg = "This alien landing came quite unexpected. Communication has been garbled."))
+>>>
+pushInput(
+ closeFrame(
+ Protocol.CloseCodes.UnexpectedCondition,
+ mask = true,
+ msg = "This alien landing came quite unexpected. Communication has been garbled."
+ )
+)
+<<< #1802
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+===
+`WWW-Authenticate`(
+ HttpChallenge("Digest",
+ "testrealm@host.com",
+ Map("qop" -> "auth,auth-int",
+ "nonce" -> "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+ "opaque" -> "5ccc069c403ebaf9f0171e9517f40e41")))
+ .renderedTo(
+ "Digest realm=\"testrealm@host.com\",qop=\"auth,auth-int\",nonce=dcd98b7102dd2f0e8b11d0f600bfb0c093,opaque=5ccc069c403ebaf9f0171e9517f40e41")
+>>>
+`WWW-Authenticate`(
+ HttpChallenge(
+ "Digest",
+ "testrealm@host.com",
+ Map(
+ "qop" -> "auth,auth-int",
+ "nonce" -> "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+ "opaque" -> "5ccc069c403ebaf9f0171e9517f40e41"
+ )
+ )
+)
+ .renderedTo(
+ "Digest realm=\"testrealm@host.com\",qop=\"auth,auth-int\",nonce=dcd98b7102dd2f0e8b11d0f600bfb0c093,opaque=5ccc069c403ebaf9f0171e9517f40e41"
+ )
+<<< #1803
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = true
+===
+Await
+ .result(
+ Unmarshal(HttpEntity(
+ `multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
+ """this is
+ |just preamble text""".stripMarginWithNewline("\r\n")))
+ .to[Multipart.General]
+ .failed,
+ 1.second)
+ .getMessage
+>>>
+Await
+ .result(
+ Unmarshal(
+ HttpEntity(
+ `multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
+ """this is
+ |just preamble text""".stripMarginWithNewline("\r\n")
+ )
+ )
+ .to[Multipart.General]
+ .failed,
+ 1.second
+ )
+ .getMessage
+<<< #1803, no breakChainOnFirstMethodDot
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+===
+Await
+ .result(
+ Unmarshal(HttpEntity(
+ `multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
+ """this is
+ |just preamble text""".stripMarginWithNewline("\r\n")))
+ .to[Multipart.General]
+ .failed,
+ 1.second)
+ .getMessage
+>>>
+Await
+ .result(
+ Unmarshal(
+ HttpEntity(
+ `multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
+ """this is
+ |just preamble text""".stripMarginWithNewline("\r\n")
+ )
+ ).to[Multipart.General].failed,
+ 1.second
+ )
+ .getMessage
+<<< #1804
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = true
+===
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report))
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+>>>
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report)
+ )
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+<<< #1804, no breakChainOnFirstMethodDot
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+===
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report))
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+>>>
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report)
+ ).settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+<<< #1804 bis
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = true
+===
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report))
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+>>>
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report)
+ )
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+<<< #1804 bis, no breakChainOnFirstMethodDot
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+===
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report))
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+ .settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+>>>
+object a {
+ lazy val mod = project(
+ "mod",
+ Seq(common, db, user, hub, security, game, analyse, evaluation, report)
+ ).settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ ).settings(
+ libraryDependencies ++= provided(play.api, play.test, RM, PRM)
+ )
+}
+<<< #1400, breaksInsideChains T, noBB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = true
+===
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }
+ .getOrElse{ // break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }
+ .getOrElse { // break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains T, noBnoB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = true
+===
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }.getOrElse{ // no break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }.getOrElse { // no break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains T, BB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = true
+===
+object Test {
+ Try {
+ "something1"
+ }
+ .recover { // break
+ case NonFatal(_) => "something2"
+ }
+ .getOrElse{ // break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // break
+ case NonFatal(_) => "something2"
+ }
+ .getOrElse { // break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains T, BnoB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = true
+===
+object Test {
+ Try {
+ "something1"
+ }
+ .recover { // break
+ case NonFatal(_) => "something2"
+ }.getOrElse{ // no break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // break
+ case NonFatal(_) => "something2"
+ }.getOrElse { // no break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains F, noBB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = false
+===
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }
+ .getOrElse{ // break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }.getOrElse { // break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains F, noBnoB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = false
+===
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }.getOrElse{ // no break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // no break
+ case NonFatal(_) => "something2"
+ }.getOrElse { // no break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains F, BB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = false
+===
+object Test {
+ Try {
+ "something1"
+ }
+ .recover { // break
+ case NonFatal(_) => "something2"
+ }
+ .getOrElse{ // break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // break
+ case NonFatal(_) => "something2"
+ }.getOrElse { // break
+ "something3"
+ }
+}
+<<< #1400, breaksInsideChains F, BnoB
+preset = intellij
+danglingParentheses.preset = true
+optIn.configStyleArguments = true
+optIn.breakChainOnFirstMethodDot = false
+optIn.breaksInsideChains = false
+===
+object Test {
+ Try {
+ "something1"
+ }
+ .recover { // break
+ case NonFatal(_) => "something2"
+ }.getOrElse{ // no break
+ "something3"
+ }
+}
+>>>
+object Test {
+ Try {
+ "something1"
+ }.recover { // break
+ case NonFatal(_) => "something2"
+ }.getOrElse { // no break
+ "something3"
+ }
+}
+<<< #1505 1
+maxColumn = 75
+align.preset = none
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ Seq(
+ "HTTP/1.1 204 12345678",
+ "90123456789012\r\n") should generalMultiParseTo(
+ Left(MessageStartError(
+ 400: StatusCode,
+ ErrorInfo(
+ "Response reason phrase exceeds the configured limit of 21 characters"))))
+}
+>>>
+object a {
+ Seq(
+ "HTTP/1.1 204 12345678",
+ "90123456789012\r\n") should generalMultiParseTo(
+ Left(MessageStartError(
+ 400: StatusCode,
+ ErrorInfo("Response reason phrase exceeds the configured limit of 21 characters"))))
+}
+<<< #1505 2 skip multiple args
+maxColumn = 75
+align.preset = none
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ response shouldEqual HttpResponse(
+ status = 302,
+ entity = HttpEntity(
+ ContentTypes.`text/html(UTF-8)`,
+ "The requested resource temporarily resides under this URI."),
+ headers = Location("/foo") :: Nil
+ )
+}
+>>>
+object a {
+ response shouldEqual HttpResponse(
+ status = 302,
+ entity = HttpEntity(
+ ContentTypes.`text/html(UTF-8)`,
+ "The requested resource temporarily resides under this URI."),
+ headers = Location("/foo") :: Nil
+ )
+}
+<<< #1505 3
+maxColumn = 75
+align.preset = none
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ "allow mapping the response" in {
+ val timeoutResponse = HttpResponse(
+ StatusCodes.EnhanceYourCalm,
+ entity =
+ "Unable to serve response within time limit, please enchance your calm.")
+ }
+}
+>>>
+object a {
+ "allow mapping the response" in {
+ val timeoutResponse = HttpResponse(
+ StatusCodes.EnhanceYourCalm,
+ entity = "Unable to serve response within time limit, please enchance your calm.")
+ }
+}
+<<< #1505 4 short unchanged
+maxColumn = 75
+align.preset = none
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ val boss = system.actorOf(Props(new Actor {
+ def receive = {
+ case "run" ⇒
+ for (_ ← 1 to num)(context.watch(
+ context.actorOf(props))) ! cachedMessage
+ case Terminated(child) ⇒ stopLatch.countDown()
+ }
+ }).withDispatcher("boss"))
+}
+>>>
+object a {
+ val boss = system.actorOf(Props(new Actor {
+ def receive = {
+ case "run" ⇒
+ for (_ ← 1 to num)(context.watch(
+ context.actorOf(props))) ! cachedMessage
+ case Terminated(child) ⇒ stopLatch.countDown()
+ }
+ }).withDispatcher("boss"))
+}
+<<< #1505 5 two args, second single letter
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ notifyError(
+ "Startup timed out. This is usually related to actor system host setting or host name resolution misconfiguration.",
+ e)
+}
+>>>
+object a {
+ notifyError(
+ "Startup timed out. This is usually related to actor system host setting or host name resolution misconfiguration.",
+ e)
+}
+<<< #2033
+align.openParenCallSite = true
+===
+class Test {
+ val testCases = List(
+ ("Old state", "Campaign data", "Expected state"),
+ (
+ 1,
+ 2,
+ 3
+ )
+ )
+}
+>>>
+class Test {
+ val testCases = List(
+ ("Old state", "Campaign data", "Expected state"),
+ (
+ 1,
+ 2,
+ 3
+ )
+ )
+}
diff --git a/scalafmt-tests/src/test/resources/default/Case.stat b/scalafmt-tests/src/test/resources/default/Case.stat
index ec8214fc93..48330b7005 100644
--- a/scalafmt-tests/src/test/resources/default/Case.stat
+++ b/scalafmt-tests/src/test/resources/default/Case.stat
@@ -200,3 +200,19 @@ x match {
) 0
else 1
}
+<<< blank line after empty case clause
+object a {
+ val a = a match {
+ case a =>
+
+ case b =>
+ }
+}
+>>>
+object a {
+ val a = a match {
+ case a =>
+
+ case b =>
+ }
+}
diff --git a/scalafmt-tests/src/test/resources/default/Lambda.stat b/scalafmt-tests/src/test/resources/default/Lambda.stat
index 720d5cee17..d78a470cf0 100644
--- a/scalafmt-tests/src/test/resources/default/Lambda.stat
+++ b/scalafmt-tests/src/test/resources/default/Lambda.stat
@@ -437,19 +437,30 @@ class A {
class A {
def foo: B =
a.b() { c =>
- val d = c.add(
- D[E](
- 1,
- {
- case conditionNameShouldLongEnough =>
- foo1(objectNameShouldLongEnough, objectNameShouldLongEnough)
- 0
- case conditionNameShouldLongEnough =>
- foo1(objectNameShouldLongEnough, objectNameShouldLongEnough)
- 1
- }
- )
+ val d = c.add(
+ D[E](
+ 1,
+ {
+ case conditionNameShouldLongEnough =>
+ foo1(objectNameShouldLongEnough, objectNameShouldLongEnough)
+ 0
+ case conditionNameShouldLongEnough =>
+ foo1(objectNameShouldLongEnough, objectNameShouldLongEnough)
+ 1
+ }
)
- }
- .f("f")
+ )
+ }.f("f")
+}
+<<< #1969
+object a {
+ And("foo") { () =>
+ // bar
+ }
+}
+>>>
+object a {
+ And("foo") { () =>
+ // bar
+ }
}
diff --git a/scalafmt-tests/src/test/resources/default/Select.stat b/scalafmt-tests/src/test/resources/default/Select.stat
index 9f98be3460..e1dc6a7641 100644
--- a/scalafmt-tests/src/test/resources/default/Select.stat
+++ b/scalafmt-tests/src/test/resources/default/Select.stat
@@ -62,7 +62,7 @@ val lastToken = owner.body.tokens.filter {
case _: Whitespace | _: Comment => false
case _ => true
} // edge case, if body is empty expire on arrow.
-.lastOption.getOrElse(arrow)
+ .lastOption.getOrElse(arrow)
<<< apply infix has indent
val expireAAAAAAAAAAAAAAAAAAAAA = owner.tokens.
find(t => t.isInstanceOf[`=`] && owners(t) == owner)
@@ -278,3 +278,623 @@ object a {
keys.clear() // we need to remove the selected keys from the set, otherwise they remain selected
>>>
keys.clear() // we need to remove the selected keys from the set, otherwise they remain selected
+<<< #2042 1
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+===
+object a {
+ val res = Seq(1, 2, 3)
+ // Keep odd numbers
+ .filter(_ % 2 == 1)
+}
+>>>
+object a {
+ val res = Seq(1, 2, 3)
+ // Keep odd numbers
+ .filter(_ % 2 == 1)
+}
+<<< #2042 2
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+===
+object a {
+ (TypedPipe.from[(Int, Int)](Tsv("in0", (0, 1)), (0, 1))
+ joinBy TypedPipe.from[(Int, Int)](Tsv("in1", (0, 1)), (0, 1)))(_._1, _._1)
+ //Flatten out to three values:
+ .toTypedPipe
+ .map { kvw => (kvw._1, kvw._2._1._2, kvw._2._2._2) }
+ .write(TypedText.tsv[(Int, Int, Int)]("out2"))
+}
+>>>
+object a {
+ (TypedPipe.from[(Int, Int)](Tsv("in0", (0, 1)), (0, 1))
+ joinBy TypedPipe.from[(Int, Int)](Tsv("in1", (0, 1)), (0, 1)))(_._1, _._1)
+ //Flatten out to three values:
+ .toTypedPipe
+ .map { kvw => (kvw._1, kvw._2._1._2, kvw._2._2._2) }
+ .write(TypedText.tsv[(Int, Int, Int)]("out2"))
+}
+<<< #2061 1
+object a {
+@SerialVersionUID(1)
+ class VectorBuilder[@spec(Double, Int, Float, Long) E](
+ private var _index: Array[Int],
+ private var _data: Array[E],
+ private var used: Int,
+ var length: Int)(implicit
+ ring: Semiring[E],
+ zero: Zero[E])
+ extends NumericOps[VectorBuilder[E]]
+ with Serializable {
+
+ }
+}
+>>>
+object a {
+ @SerialVersionUID(1)
+ class VectorBuilder[@spec(Double, Int, Float, Long) E](
+ private var _index: Array[Int],
+ private var _data: Array[E],
+ private var used: Int,
+ var length: Int)(implicit ring: Semiring[E], zero: Zero[E])
+ extends NumericOps[VectorBuilder[E]]
+ with Serializable {}
+}
+<<< #2061 2
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+===
+object a {
+ object b {
+ val existingNamespace = client.admin.listNamespaceDescriptors()
+ .map(_.getName)
+ val existingNamespace = client.admin
+ .listNamespaceDescriptors()
+ .map(_.getName)
+ }
+ def insertAndGetEvents(eventClient: LEvents) = {
+ val insertedEvent: List[Option[Event]] = listOfEvents.zip(insertedEventId)
+ .map { case (e, id) => Some(e.copy(eventId = Some(id))) }
+ }
+}
+>>>
+object a {
+ object b {
+ val existingNamespace = client.admin
+ .listNamespaceDescriptors()
+ .map(_.getName)
+ val existingNamespace = client.admin
+ .listNamespaceDescriptors()
+ .map(_.getName)
+ }
+ def insertAndGetEvents(eventClient: LEvents) = {
+ val insertedEvent: List[Option[Event]] = listOfEvents
+ .zip(insertedEventId)
+ .map { case (e, id) => Some(e.copy(eventId = Some(id))) }
+ }
+}
+<<< #2061 3
+preset = default
+===
+object a {
+ val algoPredictsMap: Map[EX, RDD[(QX, Seq[P])]] = (0 until evalCount)
+ .map { ex => {
+ val unionAlgoPredicts: RDD[(QX, Seq[P])] = sc.union(algoPredicts)
+ .groupByKey()
+ .mapValues { ps => {
+ assert (ps.size == algoCount, "Must have same length as algoCount")
+ // TODO. Check size == algoCount
+ ps.toSeq.sortBy(_._1).map(_._2)
+ }}
+ }
+ }
+}
+>>>
+object a {
+ val algoPredictsMap: Map[EX, RDD[(QX, Seq[P])]] = (0 until evalCount)
+ .map { ex =>
+ {
+ val unionAlgoPredicts: RDD[(QX, Seq[P])] = sc
+ .union(algoPredicts)
+ .groupByKey()
+ .mapValues { ps =>
+ {
+ assert(ps.size == algoCount, "Must have same length as algoCount")
+ // TODO. Check size == algoCount
+ ps.toSeq.sortBy(_._1).map(_._2)
+ }
+ }
+ }
+ }
+}
+<<< #2061 4
+preset = default
+optIn.breakChainOnFirstMethodDot = false
+includeNoParensInSelectChains = false
+===
+object a {
+ override def extraTests(): Unit = {
+ "decode concatenated compressions" in {
+ ourDecode(
+ Seq(
+ encode("Hello, "),
+ encode("dear "),
+ encode("User!")).join) should readAs("Hello, dear User!")
+ }
+ }
+}
+>>>
+object a {
+ override def extraTests(): Unit = {
+ "decode concatenated compressions" in {
+ ourDecode(
+ Seq(encode("Hello, "), encode("dear "), encode("User!")).join
+ ) should readAs("Hello, dear User!")
+ }
+ }
+}
+<<< #2061 5
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+===
+object a {
+ def a = {
+ try {
+ pmmObject.instance
+ .asInstanceOf[PersistentModelLoader[AP, M]](runId, params, sc)
+ pmmObject.instance.asInstanceOf[PersistentModelLoader[AP, M]](
+ runId,
+ params,
+ sc)
+ }
+ }
+}
+>>>
+object a {
+ def a = {
+ try {
+ pmmObject.instance
+ .asInstanceOf[PersistentModelLoader[AP, M]](runId, params, sc)
+ pmmObject.instance
+ .asInstanceOf[PersistentModelLoader[AP, M]](runId, params, sc)
+ }
+ }
+}
+<<< #2061 6
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+===
+object a {
+ def a = {
+ try {
+ val a =
+ actorSystem.scheduler.schedule(
+ 0.seconds,
+ 1.days,
+ upgrade,
+ UpgradeCheck())
+ }
+ }
+}
+>>>
+object a {
+ def a = {
+ try {
+ val a =
+ actorSystem.scheduler.schedule(
+ 0.seconds,
+ 1.days,
+ upgrade,
+ UpgradeCheck()
+ )
+ }
+ }
+}
+<<< #2061 7
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+===
+object a {
+ def a = {
+ try {
+ val driver = extendedSystem.dynamicAccess
+ .createInstanceFor[Transport](fqn, args)
+ .recover({
+
+ case exception ⇒
+ throw new IllegalArgumentException(
+ s"Cannot instantiate transport [$fqn]. " +
+ "Make sure it extends [akka.remote.transport.Transport] and has constructor with " +
+ "[akka.actor.ExtendedActorSystem] and [com.typesafe.config.Config] parameters",
+ exception)
+
+ })
+ .get
+ }
+ }
+}
+>>>
+object a {
+ def a = {
+ try {
+ val driver = extendedSystem.dynamicAccess
+ .createInstanceFor[Transport](fqn, args)
+ .recover({
+
+ case exception ⇒
+ throw new IllegalArgumentException(
+ s"Cannot instantiate transport [$fqn]. " +
+ "Make sure it extends [akka.remote.transport.Transport] and has constructor with " +
+ "[akka.actor.ExtendedActorSystem] and [com.typesafe.config.Config] parameters",
+ exception
+ )
+
+ })
+ .get
+ }
+ }
+}
+<<< #2061 8
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+===
+object a {
+ val identifier: P[Ast.identifier] =
+ P((letter | "_") ~ (letter | digit | "_").rep).!.filter(
+ !keywordList.contains(_)).map(Ast.identifier)
+ def a = {
+ a match {
+ case (
+ com.twitter.finagle.exp.Address
+ .ServiceFactory(sf: ServiceFactory[Req, Rep], _),
+ _) =>
+ sf
+ }
+ }
+}
+>>>
+object a {
+ val identifier: P[Ast.identifier] =
+ P((letter | "_") ~ (letter | digit | "_").rep).!.filter(
+ !keywordList.contains(_)
+ ).map(Ast.identifier)
+ def a = {
+ a match {
+ case (
+ com.twitter.finagle.exp.Address
+ .ServiceFactory(sf: ServiceFactory[Req, Rep], _),
+ _
+ ) =>
+ sf
+ }
+ }
+}
+<<< #2061 9
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+===
+object a {
+ "produce one-to-several transformation as expected" in assertAllStagesStopped {
+ val p2 = Source
+ .fromPublisher(p)
+ .transform(() ⇒
+ new StatefulStage[Int, Int] {
+ var tot = 0
+
+ lazy val waitForNext = new State {
+ }
+ })
+ }
+}
+>>>
+object a {
+ "produce one-to-several transformation as expected" in assertAllStagesStopped {
+ val p2 = Source
+ .fromPublisher(p)
+ .transform(() ⇒
+ new StatefulStage[Int, Int] {
+ var tot = 0
+
+ lazy val waitForNext = new State {}
+ }
+ )
+ }
+}
+<<< #2061 10
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+===
+object a {
+ def a = {
+ try {
+ (r.param("args").map { args =>
+ {args.split(",").map(_.toInt).reduceLeft(operation)}
+ }) ?~ "Missing args"
+ }
+ }
+}
+>>>
+object a {
+ def a = {
+ try {
+ (r.param("args").map { args =>
+ {args.split(",").map(_.toInt).reduceLeft(operation)}
+ }) ?~ "Missing args"
+ }
+ }
+}
+<<< #2061 11
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+danglingParentheses.preset = false
+===
+object a {
+ def a = {
+ (BinaryFormat.clock(since).read(ByteArray.parseBytes(bytes), false, false))(
+ chess.White)
+ }
+}
+>>>
+object a {
+ def a = {
+ (BinaryFormat.clock(since).read(ByteArray.parseBytes(bytes), false, false))(
+ chess.White)
+ }
+}
+<<< #2061 12
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+danglingParentheses.preset = false
+===
+object a {
+ def a = {
+ try {
+ if (patt.getParent /*list of ids*/ .getParent
+ .isInstanceOf[ScVariable]) {
+ kinds contains VAR
+ val isNamedDynamic =
+ r.isDynamic && r.name == ResolvableReferenceExpression
+ .APPLY_DYNAMIC_NAMED
+ }
+ else kinds contains VAL
+ }
+ }
+}
+>>>
+object a {
+ def a = {
+ try {
+ if (patt.getParent /*list of ids*/ .getParent
+ .isInstanceOf[ScVariable]) {
+ kinds contains VAR
+ val isNamedDynamic =
+ r.isDynamic && r.name == ResolvableReferenceExpression.APPLY_DYNAMIC_NAMED
+ } else kinds contains VAL
+ }
+ }
+}
+<<< #2061 13
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+danglingParentheses.preset = false
+===
+object a {
+ def getReturnType: PsiType = {
+ if (DumbService
+ .getInstance(getProject)
+ .isDumb || !SyntheticClasses.get(getProject).isClassesRegistered) {
+ return null //no resolve during dumb mode or while synthetic classes is not registered
+ }
+ }
+}
+>>>
+object a {
+ def getReturnType: PsiType = {
+ if (DumbService
+ .getInstance(getProject)
+ .isDumb || !SyntheticClasses.get(getProject).isClassesRegistered) {
+ return null //no resolve during dumb mode or while synthetic classes is not registered
+ }
+ }
+}
+<<< #2061 14
+preset = default
+optIn.breakChainOnFirstMethodDot = false
+includeNoParensInSelectChains = false
+danglingParentheses.preset = false
+===
+object a {
+ class a {
+ def a = {
+ "zip" in {
+ intercept[IllegalStateException] {
+ Await.result(
+ Promise
+ .failed[String](f)
+ .future zip Promise.successful("foo").future,
+ timeout)
+ } should ===(f)
+ }
+ }
+ }
+}
+>>>
+object a {
+ class a {
+ def a = {
+ "zip" in {
+ intercept[IllegalStateException] {
+ Await.result(
+ Promise
+ .failed[String](f)
+ .future zip Promise.successful("foo").future,
+ timeout)
+ } should ===(f)
+ }
+ }
+ }
+}
+<<< #2061 14.1
+preset = default
+optIn.breakChainOnFirstMethodDot = false
+includeNoParensInSelectChains = true
+optIn.breaksInsideChains = true
+danglingParentheses.preset = false
+===
+object a {
+ class a {
+ def a = {
+ "zip" in {
+ intercept[IllegalStateException] {
+ Await.result(Promise.failed[String](f).future zip Promise.successful("foo").future, timeout)
+ } should ===(f)
+ }
+ }
+ }
+}
+>>>
+object a {
+ class a {
+ def a = {
+ "zip" in {
+ intercept[IllegalStateException] {
+ Await.result(
+ Promise
+ .failed[String](f).future zip Promise.successful("foo").future,
+ timeout)
+ } should ===(f)
+ }
+ }
+ }
+}
+<<< #2061 14.2
+preset = default
+optIn.breakChainOnFirstMethodDot = false
+includeNoParensInSelectChains = true
+optIn.breaksInsideChains = false
+danglingParentheses.preset = false
+===
+object a {
+ class a {
+ if (!cell
+ .routerConfig
+ .isInstanceOf[Pool] && !cell.routerConfig.isInstanceOf[Group])
+ throw ActorInitializationException(
+ "Cluster router actor can only be used with Pool or Group, not with " +
+ cell.routerConfig.getClass)
+ }
+}
+>>>
+object a {
+ class a {
+ if (!cell
+ .routerConfig
+ .isInstanceOf[Pool] && !cell.routerConfig.isInstanceOf[Group])
+ throw ActorInitializationException(
+ "Cluster router actor can only be used with Pool or Group, not with " +
+ cell.routerConfig.getClass)
+ }
+}
+<<< #2061 15
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+danglingParentheses.preset = false
+===
+object a {
+ def a = {
+ try {
+ intercept[IllegalStateException] {
+ if (sqlSerDe == null || sqlSerDe._2 == null || !(sqlSerDe._2)(
+ dos,
+ value)) {
+ writeType(dos, "jobj")
+ writeJObj(dos, value)
+ }
+ } should ===(f)
+ }
+ }
+}
+>>>
+object a {
+ def a = {
+ try {
+ intercept[IllegalStateException] {
+ if (sqlSerDe == null || sqlSerDe._2 == null || !(sqlSerDe._2)(
+ dos,
+ value)) {
+ writeType(dos, "jobj")
+ writeJObj(dos, value)
+ }
+ } should ===(f)
+ }
+ }
+}
+<<< #2061 16
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+includeNoParensInSelectChains = false
+danglingParentheses.preset = false
+===
+object a {
+ def a = {
+ val paramFieldId: Box[String] = (stuff.collect {
+ case FormFieldId(id) => id
+ }).headOption
+ }
+}
+>>>
+object a {
+ def a = {
+ val paramFieldId: Box[String] = (stuff.collect {
+ case FormFieldId(id) => id
+ }).headOption
+ }
+}
+<<< curly select chain followed by infix
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+===
+object a {
+ val reduces = node
+ .foldDown[List[dag.Reduce]](true) {
+ case r: dag.Reduce => List(r)
+ } distinct
+}
+>>>
+object a {
+ val reduces = node
+ .foldDown[List[dag.Reduce]](true) {
+ case r: dag.Reduce => List(r)
+ } distinct
+}
+<<< #2070
+preset = default
+optIn.breakChainOnFirstMethodDot = true
+===
+object Dummy {
+ def dummy(list: List[Int]): List[Int] =
+ list
+ .map(_ + 1)
+ // format: off
+ .map(_ * 2)
+ // format: on
+}
+>>>
+object Dummy {
+ def dummy(list: List[Int]): List[Int] =
+ list
+ .map(_ + 1)
+ // format: off
+ .map(_ * 2)
+ // format: on
+}
diff --git a/scalafmt-tests/src/test/resources/default/String.stat b/scalafmt-tests/src/test/resources/default/String.stat
index 6dccbd4fca..7c7906fae1 100644
--- a/scalafmt-tests/src/test/resources/default/String.stat
+++ b/scalafmt-tests/src/test/resources/default/String.stat
@@ -14,6 +14,15 @@ val x = """Short line
|Long line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
"""
>>>
+val x = """Short line
+ |Long line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ """
+<<< column limit 2
+val x =
+ """Short line
+ |Long line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ """
+>>>
val x =
"""Short line
|Long line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -65,3 +74,239 @@ object Foo {
else renderInner(wrapped)
}
}
+<<< #1640 class with multiline parent ctor
+class A(a: Int, b: String) extends B("""
+foo""") with C
+>>>
+class A(a: Int, b: String)
+ extends B("""
+foo""")
+ with C
+<<< #1640 select chain with multiline arg
+object a {
+ foo.B("""
+ |foo""").bar
+}
+>>>
+object a {
+ foo
+ .B("""
+ |foo""")
+ .bar
+}
+<<< #1640 single select with multiline arg
+object a {
+ foo.B("""
+ |foo""")
+}
+>>>
+object a {
+ foo.B("""
+ |foo""")
+}
+<<< #1640 yield with multiline arg
+object a {
+ for (n ← 1 to 30) yield """
+ test-dispatcher-%s {
+ type = "akka.actor.dispatch.DispatcherModelSpec$MessageDispatcherInterceptorConfigurator"
+ }""".format(n).mkString
+}
+>>>
+object a {
+ for (n ← 1 to 30)
+ yield """
+ test-dispatcher-%s {
+ type = "akka.actor.dispatch.DispatcherModelSpec$MessageDispatcherInterceptorConfigurator"
+ }""".format(n).mkString
+}
+<<< #1505
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ foo_bar_baz("Qui et et consectetur atque. Ex blanditiis eius omnis id. Modi est quibusdam error inventore facere vero id. Inventore in sequi praesentium consequuntur ut. Ex mollitia recusandae.") {
+ println("")
+ }
+}
+>>>
+object a {
+ foo_bar_baz("Qui et et consectetur atque. Ex blanditiis eius omnis id. Modi est quibusdam error inventore facere vero id. Inventore in sequi praesentium consequuntur ut. Ex mollitia recusandae.") {
+ println("")
+ }
+}
+<<< #1505 1
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ system.log.warning(
+ "Replicator points to dead letters: Make sure the cluster node is not terminated and has the proper role!")
+}
+>>>
+object a {
+ system.log.warning("Replicator points to dead letters: Make sure the cluster node is not terminated and has the proper role!")
+}
+<<< #1505 2
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ intercept[TestException] {
+ val ct =
+ Thread.currentThread() // Ensure that the thunk is executed in the tests thread
+ }
+}
+>>>
+object a {
+ intercept[TestException] {
+ val ct =
+ Thread.currentThread() // Ensure that the thunk is executed in the tests thread
+ }
+}
+<<< #1505 3
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ intercept[TestException] {
+ val ct = Thread.currentThread() // Ensure that the thunk is executed in the tests thread
+ }
+}
+>>>
+object a {
+ intercept[TestException] {
+ val ct = Thread.currentThread() // Ensure that the thunk is executed in the tests thread
+ }
+}
+<<< #1505 4
+continuationIndent.callSite = 2
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object RemoteRestartedQuarantinedSpec extends MultiNodeConfig {
+ commonConfig(debugConfig(on = false).withFallback(ConfigFactory.parseString("""
+ // Important, otherwise it is very racy to get a non-writing endpoint: the only way to do it if the two nodes
+ // associate to each other at the same time. Setting this will ensure that the right scenario happens.
+ """)))
+}
+>>>
+object RemoteRestartedQuarantinedSpec extends MultiNodeConfig {
+ commonConfig(debugConfig(on = false).withFallback(ConfigFactory.parseString("""
+ // Important, otherwise it is very racy to get a non-writing endpoint: the only way to do it if the two nodes
+ // associate to each other at the same time. Setting this will ensure that the right scenario happens.
+ """)))
+}
+<<< #1505 5
+maxColumn = 60
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ x.responsePromise.failure(new BufferOverflowException(
+ s"Exceeded configured max-open-requests value of [${inputBuffer.capacity}]"))
+}
+>>>
+object a {
+ x.responsePromise.failure(new BufferOverflowException(
+ s"Exceeded configured max-open-requests value of [${inputBuffer.capacity}]"))
+}
+<<< #1505 6
+maxColumn = 60
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ (
+ StatusCodes.Forbidden,
+ Map(
+ "message" -> s"${event.event} events are not allowed ${c.d}"))
+}
+>>>
+object a {
+ (StatusCodes.Forbidden,
+ Map("message" -> s"${event.event} events are not allowed ${c.d}"))
+}
+<<< #1505 7
+maxColumn = 40
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ (
+ StatusCodes.Forbidden,
+ Map(s"${event.event} events are not allowed ${c.d}"))
+}
+>>>
+object a {
+ (StatusCodes.Forbidden,
+ Map(s"${event.event} events are not allowed ${c.d}"))
+}
+<<< #1505 8
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ b match {
+ case _ =>
+ Future.failed[Any](new IllegalArgumentException(
+ s"""Unsupported recipient ActorRef type, question not sent to [$actorSel]. Sender[$sender] sent the message of type "${message.getClass.getName}"."""))
+ }
+}
+>>>
+object a {
+ b match {
+ case _ =>
+ Future.failed[Any](new IllegalArgumentException(
+ s"""Unsupported recipient ActorRef type, question not sent to [$actorSel]. Sender[$sender] sent the message of type "${message.getClass.getName}"."""))
+ }
+}
+<<< #1505 9
+newlines.avoidForSimpleOverflow = [toolong]
+===
+object a {
+ def b = {
+ try {
+ if (false) {}
+ else {
+ info("Uber JAR disabled, but current working directory does not look " +
+ s"like an engine project directory. Please delete lib/${core.getName} manually.")
+ }
+ }
+ }
+}
+>>>
+object a {
+ def b = {
+ try {
+ if (false) {} else {
+ info("Uber JAR disabled, but current working directory does not look " +
+ s"like an engine project directory. Please delete lib/${core.getName} manually.")
+ }
+ }
+ }
+}
+<<< #2026 borrow example only; don't allow multiple delay
+newlines.avoidForSimpleOverflow = [ tooLong ]
+===
+object X {
+ def f(s: Any): X.type = this
+
+ def run: Unit = {
+
+ X
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ .f {
+ X
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ }
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ }
+}
+>>>
+object X {
+ def f(s: Any): X.type = this
+
+ def run: Unit = {
+
+ X.f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ .f {
+ X.f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ }
+ .f(" sldkfjdslkf sdlkfj dslkfjds flkdsjf lsdkjf dslkfjds sdklfjsd sdlkjfdskj")
+ }
+}
diff --git a/scalafmt-tests/src/test/resources/default/Trait.stat b/scalafmt-tests/src/test/resources/default/Trait.stat
index aba022d771..a131b2f5be 100644
--- a/scalafmt-tests/src/test/resources/default/Trait.stat
+++ b/scalafmt-tests/src/test/resources/default/Trait.stat
@@ -112,6 +112,9 @@ align.preset = none
with Serializable { }
>>>
trait CounterLike[
- K, V, +M <: scala.collection.mutable.Map[K, V], +This <: Counter[K, V]]
+ K,
+ V,
+ +M <: scala.collection.mutable.Map[K, V],
+ +This <: Counter[K, V]]
extends TensorLike[K, V, This]
with Serializable {}
diff --git a/scalafmt-tests/src/test/resources/newdefault/SearchState.stat b/scalafmt-tests/src/test/resources/newdefault/SearchState.stat
index 7a669728ac..517f607258 100644
--- a/scalafmt-tests/src/test/resources/newdefault/SearchState.stat
+++ b/scalafmt-tests/src/test/resources/newdefault/SearchState.stat
@@ -99,8 +99,11 @@ val boss = system.actorOf(Props(new Actor {
requestMethod = HttpMethods.HEAD,
response = HttpResponse(
headers = List(Age(30)),
- entity = HttpEntity
- .Default(ContentTypes.`text/plain(UTF-8)`, 100, Source.empty)
+ entity = HttpEntity.Default(
+ ContentTypes.`text/plain(UTF-8)`,
+ 100,
+ Source.empty
+ )
)
) should renderTo(
"""HTTP/1.1 200 OK
diff --git a/scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat b/scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat
index 5b03802d69..cb742a6172 100644
--- a/scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat
+++ b/scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat
@@ -389,6 +389,8 @@ def f = {
"some very very very very long string which we don't want to be split"))
}
<<< 12: single-arg single-stmt long-splittable-body lambda call
+newlines.avoidForSimpleOverflow = [toolong]
+===
def f = {
something.call (
x =>
@@ -398,8 +400,7 @@ def f = {
>>>
def f = {
something.call(x =>
- g.copy(a =
- "some very very very very long string which we don't want to be split at all"))
+ g.copy(a = "some very very very very long string which we don't want to be split at all"))
}
<<< 13: single-arg single-stmt medium-splittable-body with multiple args lambda call
def f = {
diff --git a/scalafmt-tests/src/test/resources/newlines/source_classic.stat b/scalafmt-tests/src/test/resources/newlines/source_classic.stat
index d04363f3bd..13838cc30a 100644
--- a/scalafmt-tests/src/test/resources/newlines/source_classic.stat
+++ b/scalafmt-tests/src/test/resources/newlines/source_classic.stat
@@ -2,6 +2,7 @@ align.preset = none
maxColumn = 40
# newlines.source = classic
runner.optimizer.forceConfigStyleOnOffset = 50
+binPack.parentConstructors = OnelineIfPrimaryOneline
<<< 1.1: block, if-else, line too long
if (true) { println(aaaaaaaaaaaaaaaaaaaaaaaaaa)}
>>>
@@ -1930,19 +1931,16 @@ private lazy val subNode
((opt(
'*'
) ~ '[' ~> attrName <~ '+' ~ ']' ^^ {
- name =>
- AttrAppendSubNode(name)
+ name => AttrAppendSubNode(name)
}) |
(opt(
'*'
) ~ '[' ~> attrName <~ '!' ~ ']' ^^ {
- name =>
- AttrRemoveSubNode(name)
+ name => AttrRemoveSubNode(name)
}) | (opt(
'*'
) ~ '[' ~> attrName <~ ']' ^^ {
- name =>
- AttrSubNode(name)
+ name => AttrSubNode(name)
}) |
('!' ~ '!' ^^ (a =>
@@ -2304,8 +2302,8 @@ class Foo {
>>>
class Foo {
val vv = v.aaa //
- //
- .bbb
+ //
+ .bbb
.ccc()
val vv = v.aaa //
val vv = v.aaa
@@ -2322,10 +2320,10 @@ class Foo {
>>>
class Foo {
val vv = v.aaa //
- .bbb //
- //
- .ccc //
- .ddd
+ .bbb //
+ //
+ .ccc //
+ .ddd
.eee()
}
<<< #1334 3: continue chain indent after a comment with apply
@@ -2371,7 +2369,7 @@ class Foo {
.tail
>>>
val a: Vector[Array[Double]] = b.c
-// similarUserFeatures may not contain the requested user
+ // similarUserFeatures may not contain the requested user
.map { x =>
similarUserFeatures.get(x)
}
@@ -2391,8 +2389,8 @@ val a: Vector[Array[Double]] = b.c
}
>>>
val a: Vector[Array[Double]] = b.c
-// Only handle first case, others will be fixed on the next pass.
-.headOption.a match {
+ // Only handle first case, others will be fixed on the next pass.
+ .headOption.a match {
case None =>
case _ =>
}
@@ -2457,3 +2455,71 @@ object a {
baz: Seq[String] = Seq.empty)(
f: HttpResponse => U): U = qux
}
+<<< #1973 1
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ object Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ object Foo
+ extends Bar with Baz
+}
+<<< #1973 2
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String,
+ )
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String
+ ) extends Bar
+ with Baz
+}
+<<< #1973 3
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ trait Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ trait Foo
+ extends Bar with Baz
+}
+<<< #1989
+maxColumn = 80
+===
+object a {
+ val schema = StructType(Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ ))
+}
+>>>
+object a {
+ val schema = StructType(
+ Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ )
+ )
+}
diff --git a/scalafmt-tests/src/test/resources/newlines/source_fold.stat b/scalafmt-tests/src/test/resources/newlines/source_fold.stat
index 6519435f5f..d3681e5aed 100644
--- a/scalafmt-tests/src/test/resources/newlines/source_fold.stat
+++ b/scalafmt-tests/src/test/resources/newlines/source_fold.stat
@@ -1522,7 +1522,19 @@ val foo = new Bar { () => f(42) }
val foo = new Bar { () => f(42) }
>>>
val foo = new Bar { () => f(42) }
-<<< fold ctor body with wildcard arg
+<<< fold ctor body with wildcard arg, selfAnnotationNewline = true
+optIn.selfAnnotationNewline = true
+===
+val foo = new Bar { _ =>
+ println("a")
+}
+>>>
+val foo = new Bar {
+ _ => println("a")
+}
+<<< fold ctor body with wildcard arg, selfAnnotationNewline = false
+optIn.selfAnnotationNewline = false
+===
val foo = new Bar { _ =>
println("a")
}
@@ -1549,10 +1561,8 @@ val a =
8 % 9
}
>>>
-val a =
- 1 + 2 * 3 && 4 ^ 5 || 6 op 7 map {
- 8 % 9
- }
+val a = 1 + 2 * 3 && 4 ^ 5 || 6 op
+ 7 map { 8 % 9 }
<<< 8.2: infix with shorter left ||
val a = 1 + 2 * 3 || 4 ^ 5 && 6 op 7 map { 8 % 9 }
>>>
@@ -1770,8 +1780,7 @@ val a = b match {
}
>>>
val a = b match {
- case i: EngineInstance =>
- JObject(
+ case i: EngineInstance => JObject(
JField("id", JString(i.id)) :: JField("status", JString(i.status)) ::
JField("startTime", JString(i.startTime.toString)) ::
JField("endTime", JString(i.endTime.toString)) ::
@@ -2345,3 +2354,70 @@ object a {
def b =
foo.bar.baz.qux("blah blah blah")
}
+<<< #1973 1
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ object Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ object Foo
+ extends Bar with Baz
+}
+<<< #1973 2
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String,
+ )
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String
+ ) extends Bar with Baz
+}
+<<< #1973 3
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ trait Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ trait Foo
+ extends Bar with Baz
+}
+<<< #1989
+maxColumn = 80
+===
+object a {
+ val schema = StructType(
+ Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ )
+ )
+}
+>>>
+object a {
+ val schema = StructType(Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ ))
+}
diff --git a/scalafmt-tests/src/test/resources/newlines/source_keep.stat b/scalafmt-tests/src/test/resources/newlines/source_keep.stat
index ec250b935c..35fc69bbf0 100644
--- a/scalafmt-tests/src/test/resources/newlines/source_keep.stat
+++ b/scalafmt-tests/src/test/resources/newlines/source_keep.stat
@@ -2,6 +2,7 @@ align.preset = none
maxColumn = 40
newlines.source = keep
runner.optimizer.forceConfigStyleOnOffset = 50
+binPack.parentConstructors = OnelineIfPrimaryOneline
<<< 1.1: block, if-else, line too long
if (true) { println(aaaaaaaaaaaaaaaaaaaaaaaaaa)}
>>>
@@ -586,7 +587,11 @@ c,
d,
efgh)
>>>
-val a = b(c, d, efgh)
+val a = b(
+ c,
+ d,
+ efgh
+)
<<< 3.6: apply config-style, break on ( and )
val a = b(
c,
@@ -689,6 +694,31 @@ object a {
})(result)))))))
}).getMessage should ===("Ur state be b0rked")
}
+<<< 3.12.2 enclosed, followed by select
+maxColumn = 80
+danglingParentheses.preset = false
+===
+object a {
+ (intercept[java.lang.IllegalStateException] {
+ wrap(result ⇒
+ actorOf(Props(new OuterActor(actorOf(Props(
+ promiseIntercept({
+ throw new IllegalStateException("Ur state be b0rked");
+ new InnerActor
+ })(result)))))))
+ }).getMessage should ===("Ur state be b0rked")
+}
+>>>
+object a {
+ (intercept[java.lang.IllegalStateException] {
+ wrap(result ⇒
+ actorOf(Props(new OuterActor(actorOf(Props(
+ promiseIntercept({
+ throw new IllegalStateException("Ur state be b0rked");
+ new InnerActor
+ })(result)))))))
+ }).getMessage should ===("Ur state be b0rked")
+}
<<< 3.13 enclosed, followed by select and infix
maxColumn = 80
danglingParentheses.preset = false
@@ -903,9 +933,10 @@ maxColumn = 80
case EndpointFailedToActivate(`endpoint`, cause) ⇒ throw cause
})
>>>
-def activationFutureFor(
- endpoint: ActorRef
-)(implicit timeout: Timeout, executor: ExecutionContext): Future[ActorRef] =
+def activationFutureFor(endpoint: ActorRef)(implicit
+ timeout: Timeout,
+ executor: ExecutionContext
+): Future[ActorRef] =
(supervisor
.ask(AwaitActivation(endpoint))(timeout))
.map[ActorRef]({
@@ -925,9 +956,10 @@ maxColumn = 80
case EndpointFailedToActivate(`endpoint`, cause) ⇒ throw cause
})
>>>
-def activationFutureFor(
- endpoint: ActorRef
-)(implicit timeout: Timeout, executor: ExecutionContext): Future[ActorRef] = {
+def activationFutureFor(endpoint: ActorRef)(implicit
+ timeout: Timeout,
+ executor: ExecutionContext
+): Future[ActorRef] = {
supervisor
.ask(AwaitActivation(endpoint))(timeout)
}
@@ -988,7 +1020,9 @@ object foo {
val a = b(c =
d
)
- val a = b(c = d)
+ val a = b(
+ c = d
+ )
val a = b(
c = d
)
@@ -1052,10 +1086,8 @@ object WrapperToHaveStatTestCaseParserWorking {
"foo",
someInt = 5
) @annot3
- @deprecated(
- value =
- "bar"
- ) def bar2 = 1
+ @deprecated(value =
+ "bar") def bar2 = 1
}
@annot @annot2
@@ -1124,10 +1156,8 @@ object WrapperToHaveStatTestCaseParserWorking {
someInt = 5
)
@annot3
- @deprecated(
- value =
- "bar"
- ) def bar2 = 1
+ @deprecated(value =
+ "bar") def bar2 = 1
}
@annot
@@ -1391,7 +1421,9 @@ object a {
!ref.isTerminated && !ref
.asInstanceOf[ActorRefWithCell]
.underlying.isInstanceOf[UnstartedCell]
- ) should ===(Seq.empty[ActorRef])
+ ) should ===(
+ Seq.empty[ActorRef]
+ )
}
<<< 6.5: chain with delayed indent after break
maxColumn = 70
@@ -1449,9 +1481,8 @@ object a {
}
>>>
object a {
- def c(
- b: List[Int]
- ): List[Int] =
+ def c(b: List[Int])
+ : List[Int] =
for {
a <- b
if (b ++ b).length >= 2
@@ -1914,15 +1945,12 @@ private lazy val subNode: Parser[SubNode] = rep(' ') ~>
>>>
private lazy val subNode: Parser[SubNode] = rep(' ') ~>
((opt('*') ~ '[' ~> attrName <~ '+' ~ ']' ^^ {
- name =>
- AttrAppendSubNode(name)
+ name => AttrAppendSubNode(name)
}) |
(opt('*') ~ '[' ~> attrName <~ '!' ~ ']' ^^ {
- name =>
- AttrRemoveSubNode(name)
+ name => AttrRemoveSubNode(name)
}) | (opt('*') ~ '[' ~> attrName <~ ']' ^^ {
- name =>
- AttrSubNode(name)
+ name => AttrSubNode(name)
}) |
('!' ~ '!' ^^ (a => DontMergeAttributes)) |
@@ -2421,3 +2449,88 @@ object a {
baz: Seq[String] = Seq.empty)(
f: HttpResponse => U): U = qux
}
+<<< #1973 1
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ object Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ object Foo
+ extends Bar with Baz
+}
+<<< #1973 2
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String,
+ )
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String
+ ) extends Bar
+ with Baz
+}
+<<< #1973 3
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ trait Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ trait Foo
+ extends Bar with Baz
+}
+<<< #1989 1
+maxColumn = 80
+===
+object a {
+ val schema = StructType(
+ Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ ))
+}
+>>>
+object a {
+ val schema = StructType(
+ Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ )
+ )
+}
+<<< #1989 2
+maxColumn = 80
+===
+object a {
+ val schema = StructType(Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ ))
+}
+>>>
+object a {
+ val schema = StructType(Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ ))
+}
diff --git a/scalafmt-tests/src/test/resources/newlines/source_unfold.stat b/scalafmt-tests/src/test/resources/newlines/source_unfold.stat
index 08664c2785..feb2ebceea 100644
--- a/scalafmt-tests/src/test/resources/newlines/source_unfold.stat
+++ b/scalafmt-tests/src/test/resources/newlines/source_unfold.stat
@@ -2,6 +2,7 @@ align.preset = none
maxColumn = 40
newlines.source = unfold
runner.optimizer.forceConfigStyleOnOffset = 50
+binPack.parentConstructors = OnelineIfPrimaryOneline
<<< 1.1: block, if-else, line too long
if (true) { println(aaaaaaaaaaaaaaaaaaaaaaaaaa)}
>>>
@@ -1746,15 +1747,15 @@ val a =
8 % 9
}
>>>
-val a =
- 1 + 2 * 3 && 4 ^ 5 || 6 op 7 map {
+val a = 1 + 2 * 3 && 4 ^ 5 || 6 op
+ 7 map {
8 % 9
}
<<< 8.2: infix with shorter left ||
val a = 1 + 2 * 3 || 4 ^ 5 && 6 op 7 map { 8 % 9 }
>>>
-val a =
- 1 + 2 * 3 || 4 ^ 5 && 6 op 7 map {
+val a = 1 + 2 * 3 || 4 ^ 5 && 6 op
+ 7 map {
8 % 9
}
<<< 8.3: infix with longer left ||, narrow
@@ -2553,3 +2554,71 @@ object a {
baz: Seq[String] = Seq.empty
)(f: HttpResponse => U): U = qux
}
+<<< #1973 1
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ object Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ object Foo
+ extends Bar with Baz
+}
+<<< #1973 2
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String,
+ )
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ case class Foo(
+ a: Boolean,
+ b: String,
+ c: String
+ ) extends Bar
+ with Baz
+}
+<<< #1973 3
+maxColumn = 25
+continuationIndent.extendSite = 2
+===
+object a {
+ trait Foo
+ extends Bar
+ with Baz
+}
+>>>
+object a {
+ trait Foo
+ extends Bar with Baz
+}
+<<< #1989
+maxColumn = 80
+===
+object a {
+ val schema = StructType(Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ ))
+}
+>>>
+object a {
+ val schema = StructType(
+ Seq(
+ StructField("fieldA", DataTypes.StringType),
+ StructField("fieldB", DataTypes.StringType)
+ )
+ )
+}
diff --git a/scalafmt-tests/src/test/resources/optIn/Annotation.stat b/scalafmt-tests/src/test/resources/optIn/Annotation.stat
index f58e0889c0..1c2dd8ae20 100644
--- a/scalafmt-tests/src/test/resources/optIn/Annotation.stat
+++ b/scalafmt-tests/src/test/resources/optIn/Annotation.stat
@@ -54,7 +54,8 @@ object WrapperToHaveStatTestCaseParserWorking {
<<< non-single ident
@bar(1) @kas("stringaaaaaaaaaaaaaawwwwwwwwwwwaaa") class Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
>>>
-@bar(1) @kas("stringaaaaaaaaaaaaaawwwwwwwwwwwaaa") class Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
+@bar(1) @kas(
+ "stringaaaaaaaaaaaaaawwwwwwwwwwwaaa") class Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
<<< non-single ident 2
@bar(1) @kas("stringaaaaaaaaaaaaaawwwwwwwwwwwaaa")
class Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
diff --git a/scalafmt-tests/src/test/resources/readme.md b/scalafmt-tests/src/test/resources/readme.md
index 721b1b9b6a..196de251c4 100644
--- a/scalafmt-tests/src/test/resources/readme.md
+++ b/scalafmt-tests/src/test/resources/readme.md
@@ -6,6 +6,29 @@ The file extension determines how the code should be parsed
* `*.case` parses as a case
* `*.source` parses as a full compilation unit
+Each file consists of a few lines of file-level configuration overrides, followed by
+one or more tests. Each test uses the following format:
+
+```
+<<< {test name}
+{provided}
+>>> [optional filename]
+{expected}
+```
+
+`[optional filename]` can be provided if we'd like to pass it to the formatter (presumably
+in order to change its behaviour; for instance, `scalafmt` works a bit differently for `.sbt`
+and `.sc` files).
+
+And `{provided}` can just contain the code to be formatted, or optionally start with a custom
+test-level configuration override separated from the code by `===`:
+
+```
+key1 = val1
+key2 = val2
+===
+code
+```
Here is an example test suite in a file `test/resources/Foo/foo.stat`:
```
diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat
index 8cc2602dad..f6d9479cbd 100644
--- a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat
+++ b/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat
@@ -834,3 +834,21 @@ object a {
object a {
scalacOptions ~= { _ filterNot (_ startsWith "-Xlint") }
}
+<<< #2012
+rewrite.redundantBraces.generalExpressions = true
+===
+object Issue {
+ def f(): Int = ???
+
+ try {} finally {
+ val _ = f()
+ }
+}
+>>>
+object Issue {
+ def f(): Int = ???
+
+ try {} finally {
+ val _ = f()
+ }
+}
diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantParens.stat b/scalafmt-tests/src/test/resources/rewrite/RedundantParens.stat
index 4a655a3a89..6ad7993d27 100644
--- a/scalafmt-tests/src/test/resources/rewrite/RedundantParens.stat
+++ b/scalafmt-tests/src/test/resources/rewrite/RedundantParens.stat
@@ -238,7 +238,8 @@ object a {
join(a, b)((x, y) =>
where(
(x.foo < maxRetry).and(y.retryCount === x.bar).and(task.status === Failed)
- ).select(task)
+ )
+ .select(task)
.on((x.id === key).and(y.id === key))
).toList
}
@@ -262,3 +263,11 @@ object a {
object a {
scalacOptions ~= (_ filterNot (_ startsWith "-Xlint"))
}
+<<< Lit.Unit preserved
+object a {
+ print("") shouldBe (())
+}
+>>>
+object a {
+ print("") shouldBe (())
+}
diff --git a/scalafmt-tests/src/test/resources/scalajs/Apply.stat b/scalafmt-tests/src/test/resources/scalajs/Apply.stat
index 9ac2385d20..5085c47985 100644
--- a/scalafmt-tests/src/test/resources/scalajs/Apply.stat
+++ b/scalafmt-tests/src/test/resources/scalajs/Apply.stat
@@ -10,6 +10,8 @@ function(
b
)
<<< penalty #248
+optIn.breakChainOnFirstMethodDot = false
+===
def iterator(): Iterator[A] =
toIterator(it
.asInstanceOf[IteratorMethodAccess]
diff --git a/scalafmt-tests/src/test/resources/scalajs/DefDef.stat b/scalafmt-tests/src/test/resources/scalajs/DefDef.stat
index c642a1d25a..de1e04590f 100644
--- a/scalafmt-tests/src/test/resources/scalajs/DefDef.stat
+++ b/scalafmt-tests/src/test/resources/scalajs/DefDef.stat
@@ -254,3 +254,37 @@ def permissionState(
def permissionState(
options: PushSubscriptionOptions = js.native): js.Promise[PushPermissionState] = js.native
}
+<<< #2078
+object a {
+ def print(optimizerHints: OptimizerHints)(
+ implicit dummy: DummyImplicit): Unit = {
+ if (optimizerHints != OptimizerHints.empty) {
+ print("@hints(")
+ print(OptimizerHints.toBits(optimizerHints).toString)
+ print(") ")
+ }
+ }
+
+ def print(flags: ApplyFlags)(
+ implicit dummy1: DummyImplicit, dummy2: DummyImplicit): Unit = {
+ if (flags.isPrivate)
+ print("private::")
+ }
+}
+>>>
+object a {
+ def print(optimizerHints: OptimizerHints)(
+ implicit dummy: DummyImplicit): Unit = {
+ if (optimizerHints != OptimizerHints.empty) {
+ print("@hints(")
+ print(OptimizerHints.toBits(optimizerHints).toString)
+ print(") ")
+ }
+ }
+
+ def print(flags: ApplyFlags)(implicit dummy1: DummyImplicit,
+ dummy2: DummyImplicit): Unit = {
+ if (flags.isPrivate)
+ print("private::")
+ }
+}
diff --git a/scalafmt-tests/src/test/resources/test/BlocksInSingleArg.stat b/scalafmt-tests/src/test/resources/test/BlocksInSingleArg.stat
index ccb60f38ea..cca626b7e7 100644
--- a/scalafmt-tests/src/test/resources/test/BlocksInSingleArg.stat
+++ b/scalafmt-tests/src/test/resources/test/BlocksInSingleArg.stat
@@ -1,5 +1,6 @@
preset = IntelliJ
optIn.configStyleArguments = true
+newlines.avoidForSimpleOverflow = [toolong]
<<< single arg with curly
println(
for (x <- Nil) yield {
@@ -45,8 +46,10 @@ foo( 1)
>>>
foo(1)
<<< #593 break line
-foo("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
+object a {
+ foo("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
+}
>>>
-foo(
- "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
-)
+object a {
+ foo("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
+}
diff --git a/scalafmt-tests/src/test/resources/test/Dangling.stat b/scalafmt-tests/src/test/resources/test/Dangling.stat
index 4b41797d87..b21b3a7cbf 100644
--- a/scalafmt-tests/src/test/resources/test/Dangling.stat
+++ b/scalafmt-tests/src/test/resources/test/Dangling.stat
@@ -57,8 +57,9 @@ new Compiler(
);
>>>
new Compiler(
- primitives.opti___________ons
- .has("primitives"),
+ primitives.opti___________ons.has(
+ "primitives"
+ ),
minify.options.has("minify"),
preserve.options.has("preserve"),
liveAnalysis.check(
diff --git a/scalafmt-tests/src/test/resources/test/Dialect.source b/scalafmt-tests/src/test/resources/test/Dialect.source
index 76eb33a5dd..23b98da77a 100644
--- a/scalafmt-tests/src/test/resources/test/Dialect.source
+++ b/scalafmt-tests/src/test/resources/test/Dialect.source
@@ -29,3 +29,16 @@ lazy val root = project
.settings(noPublish)
.aggregate(core, cli, benchmarks, scalafmtSbt, macros, readme)
.dependsOn(core)
+<<< #1716 shebang for ammonite
+runner.dialect = default
+align.preset = most
+===
+#!/usr/bin/env amm
+addSbtPlugin( "io.get-coursier" % "sbt-coursier" % "1.0.0-M14")
+addSbtPlugin( "com.eed3si9n" % "sbt-assembly" % "0.14.3")
+addSbtPlugin( "org.brianmckenna" % "sbt-wartremover" % "0.14")
+>>> foo.sc
+#!/usr/bin/env amm
+addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M14")
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
+addSbtPlugin("org.brianmckenna" % "sbt-wartremover" % "0.14")
diff --git a/scalafmt-tests/src/test/resources/test/JavaDoc.stat b/scalafmt-tests/src/test/resources/test/JavaDoc.stat
index c75cd74885..e56cc8d659 100644
--- a/scalafmt-tests/src/test/resources/test/JavaDoc.stat
+++ b/scalafmt-tests/src/test/resources/test/JavaDoc.stat
@@ -14,4 +14,1282 @@ object a {
*/
val y = 2
}
+<<< #1403 1: preserve
+docstrings = preserve
+===
+/** Top line.
+ * Main entry-point.
+ */
+class Main
+>>>
+/** Top line.
+ * Main entry-point.
+ */
+class Main
+<<< #1403 1: ScalaDoc
+docstrings = ScalaDoc
+===
+/** Top line.
+ * Main entry-point.
+ */
+class Main
+>>>
+/** Top line.
+ * Main entry-point.
+ */
+class Main
+<<< #1403 1: Asterisk 1
+docstrings.style = Asterisk
+===
+/**Top line.
+ *Main entry-point.
+ */
+class Main
+>>>
+/**
+ * Top line.
+ * Main entry-point.
+ */
+class Main
+<<< #1403 1: Asterisk 2
+docstrings.style = Asterisk
+===
+/**
+ *Main entry-point.
+ */
+class Main
+>>>
+/**
+ * Main entry-point.
+ */
+class Main
+<<< #1403 1: SpaceAsterisk 1
+docstrings.style = SpaceAsterisk
+===
+/**Top line.
+ *Main entry-point.
+ */
+class Main
+>>>
+/** Top line.
+ * Main entry-point.
+ */
+class Main
+<<< #1403 1: SpaceAsterisk 2
+docstrings.style = SpaceAsterisk
+===
+/**
+ *Main entry-point.
+ */
+class Main
+>>>
+/**
+ * Main entry-point.
+ */
+class Main
+<<< #1403 1: AsteriskSpace 1
+docstrings.style = AsteriskSpace
+===
+/**
+ *Indent0
+ * Indent1
+ * Indent2
+ * Indent3
+* Indent4
+ */
+class Main
+>>>
+/**
+ * Indent0
+ * Indent1
+ * Indent2
+ * Indent3
+ * Indent4
+ */
+class Main
+<<< #1403 1: AsteriskSpace 2
+docstrings.style = AsteriskSpace
+===
+/**Top line.
+ *Indent0
+ * Indent1
+ * Indent2
+ * Indent3
+* Indent4
+ */
+class Main
+>>>
+/** Top line.
+ * Indent0
+ * Indent1
+ * Indent2
+ * Indent3
+ * Indent4
+ */
+class Main
+<<< #1403 2: fold Asterisk
+docstrings.oneline = fold
+docstrings.style = Asterisk
+===
+/**
+ * Main entry-point.
+ */
+class Main
+>>>
+/** Main entry-point. */
+class Main
+<<< #1403 2: fold SpaceAsterisk
+docstrings.oneline = fold
+docstrings.style = SpaceAsterisk
+===
+/**
+ * Main entry-point.
+ */
+class Main
+>>>
+/** Main entry-point. */
+class Main
+<<< #1403 2: fold AsteriskSpace
+docstrings.oneline = fold
+docstrings.style = AsteriskSpace
+===
+/**
+ * Main entry-point.
+ */
+class Main
+>>>
+/** Main entry-point. */
+class Main
+<<< #1403 3: unfold Asterisk
+docstrings.oneline = unfold
+docstrings.style = Asterisk
+===
+/** Main entry-point. */
+class Main
+>>>
+/**
+ * Main entry-point.
+ */
+class Main
+<<< #1403 3: unfold SpaceAsterisk
+docstrings.oneline = unfold
+docstrings.style = SpaceAsterisk
+===
+/** Main entry-point. */
+class Main
+>>>
+/** Main entry-point.
+ */
+class Main
+<<< #1403 3: unfold AsteriskSpace
+docstrings.oneline = unfold
+docstrings.style = AsteriskSpace
+===
+/** Main entry-point. */
+class Main
+>>>
+/** Main entry-point.
+ */
+class Main
+<<< #1387 1 Asterisk
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 30
+===
+/** Start the comment here
+ *
+ * @param d the Double to square, meaning multiply by itself
+ * @returns the result of squaring d
+
+{{{
+multi
+ line
+ code
+}}}
+* {{{
+* multi
+* line
+* code
+* }}}
+@tparam t type, @see [[d]] ({{{single-line code}}})
+ */
+ val a = 1
+>>>
+/**
+ * Start the comment here
+ *
+ * @param d
+ * the Double to square,
+ * meaning multiply by
+ * itself
+ * @returns
+ * the result of squaring d
+ *
+ * {{{
+ * multi
+ * line
+ * code
+ * }}}
+ * {{{
+ * multi
+ * line
+ * code
+ * }}}
+ * @tparam t
+ * type, @see [[d]]
+ * ({{{single-line code}}})
+ */
+val a = 1
+<<< #1387 1 SpaceAsterisk
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 30
+===
+/** Start the comment here
+ *
+ * @param d the Double to square, meaning multiply by itself
+ * @returns the result of squaring d
+
+{{{
+multi
+ line
+ code
+}}}
+* {{{
+* multi
+* line
+* code
+* }}}
+@tparam t type, @see [[d]] ({{{single-line code}}})
+ */
+ val a = 1
+>>>
+/** Start the comment here
+ *
+ * @param d
+ * the Double to square,
+ * meaning multiply by
+ * itself
+ * @returns
+ * the result of squaring d
+ *
+ * {{{
+ * multi
+ * line
+ * code
+ * }}}
+ * {{{
+ * multi
+ * line
+ * code
+ * }}}
+ * @tparam t
+ * type, @see [[d]]
+ * ({{{single-line code}}})
+ */
+val a = 1
+<<< #1387 1 AsteriskSpace
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 30
+===
+/** Start the comment here
+ *
+ * @param d the Double to square, meaning multiply by itself
+ * @returns the result of squaring d
+
+{{{
+multi
+ line
+
+ code
+}}}
+* {{{
+* multi
+
+* line
+* code
+* }}}
+@tparam t type, @see [[d]] ({{{single-line code}}})
+ */
+ val a = 1
+>>>
+/** Start the comment here
+ *
+ * @param d
+ * the Double to square,
+ * meaning multiply by
+ * itself
+ * @returns
+ * the result of squaring d
+ *
+ * {{{
+ * multi
+ * line
+ *
+ * code
+ * }}}
+ * {{{
+ * multi
+ *
+ * line
+ * code
+ * }}}
+ * @tparam t
+ * type, @see [[d]]
+ * ({{{single-line code}}})
+ */
+val a = 1
+<<< #1887 1 Asterisk
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 30
+===
+/** Start the comment here
+ *
+ * @inheritdoc
+ * some text1 some text2 some text3 some text4
+ * 1. list1 list2 list3 list4 list5
+ * - sublist1 sublist2 sublist3
+ * a. subsublist1 subsublist2
+ * - foolist1 foolist2 foolist3
+ * 1. barlist1 barlist2 barlist3
+ */
+ val a = 1
+>>>
+/**
+ * Start the comment here
+ *
+ * @inheritdoc
+ * some text1 some text2 some
+ * text3 some text4
+ * 1. list1 list2 list3
+ * list4 list5
+ * - sublist1 sublist2
+ * sublist3
+ * a. subsublist1
+ * subsublist2
+ * - foolist1 foolist2
+ * foolist3
+ * 1. barlist1 barlist2
+ * barlist3
+ */
+val a = 1
+<<< #1887 1 SpaceAsterisk
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 30
+===
+/** Start the comment here
+ *
+ * @inheritdoc
+ * some text1 some text2 some text3 some text4
+ * 1. list1 list2 list3 list4 list5
+ * - sublist1 sublist2 sublist3
+ * a. subsublist1 subsublist2
+ * - foolist1 foolist2 foolist3
+ * 1. barlist1 barlist2 barlist3
+ */
+ val a = 1
+>>>
+/** Start the comment here
+ *
+ * @inheritdoc
+ * some text1 some text2 some
+ * text3 some text4
+ * 1. list1 list2 list3
+ * list4 list5
+ * - sublist1 sublist2
+ * sublist3
+ * a. subsublist1
+ * subsublist2
+ * - foolist1 foolist2
+ * foolist3
+ * 1. barlist1 barlist2
+ * barlist3
+ */
+val a = 1
+<<< #1887 1 AsteriskSpace
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 30
+===
+/** Start the comment here
+ *
+ * @inheritdoc
+ * some text1 some text2 some text3 some text4
+ * 1. list1 list2 list3 list4 list5
+ * - sublist1 sublist2 sublist3
+ * a. subsublist1 subsublist2
+ * - foolist1 foolist2 foolist3
+ * 1. barlist1 barlist2 barlist3
+ */
+ val a = 1
+>>>
+/** Start the comment here
+ *
+ * @inheritdoc
+ * some text1 some text2 some
+ * text3 some text4
+ * 1. list1 list2 list3
+ * list4 list5
+ * - sublist1 sublist2
+ * sublist3
+ * a. subsublist1
+ * subsublist2
+ * - foolist1 foolist2
+ * foolist3
+ * 1. barlist1 barlist2
+ * barlist3
+ */
+val a = 1
+<<< #1887 2 Asterisk: first line max
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 30
+===
+object a {
+ /** Binds values which should
+ * be wrapped */
+}
+>>>
+object a {
+
+ /**
+ * Binds values which should
+ * be wrapped
+ */
+}
+<<< #1887 2 SpaceAsterisk: first line max
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 30
+===
+object a {
+ /** Binds values which should
+ * be wrapped */
+}
+>>>
+object a {
+
+ /** Binds values which
+ * should be wrapped
+ */
+}
+<<< #1887 2 AsteriskSpace: first line max
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 30
+===
+object a {
+ /** Binds values which should
+ * be wrapped */
+}
+>>>
+object a {
+
+ /** Binds values which
+ * should be wrapped
+ */
+}
+<<< #1887 3 Asterisk: markdown
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 15
+# https://docs.scala-lang.org/overviews/scaladoc/for-library-authors.html#markup
+===
+object a {
+ /**
+ `mono space`
+ ''italic text''
+ '''bold text'''
+ __under line__
+ ^super script^
+ ,,sub script,,
+ 'single quoted'
+ "double quoted"
+ */
+}
+>>>
+object a {
+
+ /**
+ * `mono
+ * space`
+ * ''italic
+ * text''
+ * '''bold
+ * text'''
+ * __under
+ * line__
+ * ^super
+ * script^
+ * ,,sub
+ * script,,
+ * 'single
+ * quoted'
+ * "double
+ * quoted"
+ */
+}
+<<< #1887 3 SpaceAsterisk: markdown
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 15
+===
+object a {
+ /**
+ `mono space`
+ ''italic text''
+ '''bold text'''
+ __under line__
+ ^super script^
+ ,,sub script,,
+ 'single quoted'
+ "double quoted"
+ */
+}
+>>>
+object a {
+
+ /** `mono
+ * space`
+ * ''italic
+ * text''
+ * '''bold
+ * text'''
+ * __under
+ * line__
+ * ^super
+ * script^
+ * ,,sub
+ * script,,
+ * 'single
+ * quoted'
+ * "double
+ * quoted"
+ */
+}
+<<< #1887 3 AsteriskSpace: markdown
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 15
+===
+object a {
+ /**
+ `mono space`
+ ''italic text''
+ '''bold text'''
+ __under line__
+ ^super script^
+ ,,sub script,,
+ 'single quoted'
+ "double quoted"
+ */
+}
+>>>
+object a {
+
+ /** `mono
+ * space`
+ * ''italic
+ * text''
+ * '''bold
+ * text'''
+ * __under
+ * line__
+ * ^super
+ * script^
+ * ,,sub
+ * script,,
+ * 'single
+ * quoted'
+ * "double
+ * quoted"
+ */
+}
+<<< #1887 4 Asterisk: urls
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 23
+===
+object a {
+ /** go to
+ http:/a.b.c/d . good luck.
+ */
+}
+>>>
+object a {
+
+ /**
+ * go to
+ * http:/a.b.c/d .
+ * good luck.
+ */
+}
+<<< #1887 4 SpaceAsterisk: urls
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 24
+===
+object a {
+ /** go to
+ http:/a.b.c/d . good luck.
+ */
+}
+>>>
+object a {
+
+ /** go to
+ * http:/a.b.c/d .
+ * good luck.
+ */
+}
+<<< #1887 4 AsteriskSpace: urls
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 24
+===
+object a {
+ /** go to
+ http:/a.b.c/d . good luck.
+ */
+}
+>>>
+object a {
+
+ /** go to
+ * http:/a.b.c/d .
+ * good luck.
+ */
+}
+<<< #1887 5 Asterisk: html
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 30
+===
+object a {
+ /** go to this url
+ */
+}
+>>>
+object a {
+
+ /**
+ * go to this
+ * url
+ */
+}
+<<< #1887 5 SpaceAsterisk: html
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 30
+===
+object a {
+ /** go to this url
+ */
+}
+>>>
+object a {
+
+ /** go to this
+ * url
+ */
+}
+<<< #1887 5 AsteriskSpace: html
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 30
+===
+object a {
+ /** go to this url
+ */
+}
+>>>
+object a {
+
+ /** go to this
+ * url
+ */
+}
+<<< #1887 6 Asterisk: table
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 24
+===
+object a {
+ /**
+ * head
+ * | hdr1 | hdr22 | hdr333 |
+ * | :- | -: | :-: | -: | :-: |
+ * | r1 1 | r1 2 | r1 3 | r1 4 |
+ * | r22 1 | r22 2 |
+ * | r333 1 | r333 2 | r333 3 | r333 4 | r333 5 | r333 6 |
+ * tail
+ */
+}
+>>>
+object a {
+
+ /**
+ * head
+ * | hdr1 | hdr22 | hdr333 | | | |
+ * |:-------|-------:|:------:|-------:|:------:|:-------|
+ * | r1 1 | r1 2 | r1 3 | r1 4 | | |
+ * | r22 1 | r22 2 | | | | |
+ * | r333 1 | r333 2 | r333 3 | r333 4 | r333 5 | r333 6 |
+ * tail
+ */
+}
+<<< #1887 6 SpaceAsterisk: table
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 24
+===
+object a {
+ /**
+ * head
+ * | hdr1 | hdr22 | hdr333 |
+ * | :- | -: | :-: | -: | :-: |
+ * | r1 1 | r1 2 | r1 3 | r1 4 |
+ * | r22 1 | r22 2 |
+ * | r333 1 | r333 2 | r333 3 | r333 4 | r333 5 | r333 6 |
+ * tail
+ */
+}
+>>>
+object a {
+
+ /** head
+ * | hdr1 | hdr22 | hdr333 | | | |
+ * |:-------|-------:|:------:|-------:|:------:|:-------|
+ * | r1 1 | r1 2 | r1 3 | r1 4 | | |
+ * | r22 1 | r22 2 | | | | |
+ * | r333 1 | r333 2 | r333 3 | r333 4 | r333 5 | r333 6 |
+ * tail
+ */
+}
+<<< #1887 6 AsteriskSpace: table
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 24
+===
+object a {
+ /**
+ * head
+ * | hdr1 | hdr22 | hdr333 |
+ * | :- | -: | :-: | -: | :-: |
+ * | r1 1 | r1 2 | r1 3 | r1 4 |
+ * | r22 1 | r22 2 |
+ * | r333 1 | r333 2 | r333 3 | r333 4 | r333 5 | r333 6 |
+ * tail
+ */
+}
+>>>
+object a {
+
+ /** head
+ * | hdr1 | hdr22 | hdr333 | | | |
+ * |:-------|-------:|:------:|-------:|:------:|:-------|
+ * | r1 1 | r1 2 | r1 3 | r1 4 | | |
+ * | r22 1 | r22 2 | | | | |
+ * | r333 1 | r333 2 | r333 3 | r333 4 | r333 5 | r333 6 |
+ * tail
+ */
+}
+<<< #1887 7 Asterisk: list only
+docstrings.wrap = yes
+docstrings.style = Asterisk
+maxColumn = 24
+===
+object a {
+ /** 1. foo
+ * 1. bar
+ */
+}
+>>>
+object a {
+
+ /**
+ * 1. foo
+ * 1. bar
+ */
+}
+<<< #1887 7 SpaceAsterisk: list only
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+maxColumn = 24
+===
+object a {
+ /** 1. foo
+ * 1. bar
+ */
+}
+>>>
+object a {
+
+ /** 1. foo
+ * 1. bar
+ */
+}
+<<< #1887 7 AsteriskSpace: list only
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+maxColumn = 24
+===
+object a {
+ /** 1. foo
+ * 1. bar
+ */
+}
+>>>
+object a {
+
+ /**
+ * 1. foo
+ * 1. bar
+ */
+}
+<<< #1887 8 Asterisk: punctuation after link/code
+docstrings.wrap = yes
+docstrings.style = Asterisk
+===
+object a {
+ /** [[ref]]!.. {{{foo}}}??? */
+}
+>>>
+object a {
+
+ /** [[ref]]!.. {{{foo}}}??? */
+}
+<<< #1887 8 SpaceAsterisk: punctuation after link/code
+docstrings.wrap = yes
+docstrings.style = SpaceAsterisk
+===
+object a {
+ /** [[ref]]!.. {{{foo}}}??? */
+}
+>>>
+object a {
+ /** [[ref]]!.. {{{foo}}}??? */
+}
+<<< #1887 8 AsteriskSpace: punctuation after link/code
+docstrings.wrap = yes
+docstrings.style = AsteriskSpace
+===
+object a {
+ /** [[ref]]!.. {{{foo}}}??? */
+}
+>>>
+object a {
+
+ /** [[ref]]!.. {{{foo}}}??? */
+}
+<<< keep inner asterisks, Asterisk
+docstrings.style = Asterisk
+===
+object a {
+ /** *************** tests ******************
+ * *************** tests ****************** */
+}
+>>>
+object a {
+
+ /**
+ * *************** tests ******************
+ * *************** tests ******************
+ */
+}
+<<< keep inner asterisks, SpaceAsterisk
+docstrings.style = SpaceAsterisk
+===
+object a {
+ /** *************** tests ******************
+ * *************** tests ****************** */
+}
+>>>
+object a {
+
+ /** *************** tests ******************
+ * *************** tests ******************
+ */
+}
+<<< keep inner asterisks, AsteriskSpace
+docstrings.style = AsteriskSpace
+===
+object a {
+ /** *************** tests ******************
+ * *************** tests ****************** */
+}
+>>>
+object a {
+
+ /** *************** tests ******************
+ * *************** tests ******************
+ */
+}
+<<< empty comment
+object a {
+ /**//**/
+}
+>>>
+object a {
+
+ /**/ /**/
+}
+<<< #2027 1
+object a {
+ @foo
+ /**
+ * scaladoc
+ */
+ class QueueSequentialBenchmark {}
+}
+>>>
+object a {
+ @foo
+ /**
+ * scaladoc
+ */
+ class QueueSequentialBenchmark {}
+}
+<<< #2027 2
+object a {
+ @foo(bar)
+ /**
+ * scaladoc
+ */
+ class QueueSequentialBenchmark {}
+}
+>>>
+object a {
+ @foo(bar)
+ /**
+ * scaladoc
+ */
+ class QueueSequentialBenchmark {}
+}
+<<< #2027 3
+object a {
+ private
+ /**
+ * scaladoc
+ */
+ class QueueSequentialBenchmark {}
+}
+>>>
+object a {
+ private
+ /**
+ * scaladoc
+ */
+ class QueueSequentialBenchmark {}
+}
+<<< #2028 Asterisk keep
+docstrings.style = Asterisk
+docstrings.oneline = keep
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /**
+ * alias for [[literal]]
+ */
+}
+<<< #2028 Asterisk fold
+docstrings.style = Asterisk
+docstrings.oneline = fold
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /** alias for [[literal]] */
+}
+<<< #2028 Asterisk unfold
+docstrings.style = Asterisk
+docstrings.oneline = unfold
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /**
+ * alias for [[literal]]
+ */
+ /**
+ * alias for [[literal]]
+ */
+}
+<<< #2028 SpaceAsterisk keep
+docstrings.style = SpaceAsterisk
+docstrings.oneline = keep
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /**
+ * alias for [[literal]]
+ */
+}
+<<< #2028 SpaceAsterisk fold
+docstrings.style = SpaceAsterisk
+docstrings.oneline = fold
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /** alias for [[literal]] */
+}
+<<< #2028 SpaceAsterisk unfold
+docstrings.style = SpaceAsterisk
+docstrings.oneline = unfold
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]]
+ */
+ /**
+ * alias for [[literal]]
+ */
+}
+<<< #2028 AsteriskSpace keep
+docstrings.style = AsteriskSpace
+docstrings.oneline = keep
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /**
+ * alias for [[literal]]
+ */
+}
+<<< #2028 AsteriskSpace fold
+docstrings.style = AsteriskSpace
+docstrings.oneline = fold
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /** alias for [[literal]] */
+}
+<<< #2028 AsteriskSpace unfold
+docstrings.style = AsteriskSpace
+docstrings.oneline = unfold
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]]
+ */
+ /**
+ * alias for [[literal]]
+ */
+}
+<<< #2028 Asterisk long, fold, wrap
+docstrings.style = Asterisk
+docstrings.oneline = fold
+docstrings.wrap = yes
+maxColumn = 25
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /**
+ * alias for
+ * [[literal]]
+ */
+ /**
+ * alias for
+ * [[literal]]
+ */
+}
+<<< #2028 Asterisk long, keep, wrap
+docstrings.style = Asterisk
+docstrings.oneline = fold
+docstrings.wrap = yes
+maxColumn = 25
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /**
+ * alias for
+ * [[literal]]
+ */
+ /**
+ * alias for
+ * [[literal]]
+ */
+}
+<<< #2028 Asterisk long, fold, !wrap
+docstrings.style = Asterisk
+docstrings.oneline = fold
+docstrings.wrap = no
+maxColumn = 25
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /** alias for [[literal]] */
+}
+<<< #2028 Asterisk long, keep, !wrap
+docstrings.style = Asterisk
+docstrings.oneline = fold
+docstrings.wrap = no
+maxColumn = 25
+===
+object a {
+ /** alias for [[literal]] */
+ /**
+ *
+ * alias for [[literal]]
+ *
+ */
+}
+>>>
+object a {
+
+ /** alias for [[literal]] */
+ /** alias for [[literal]] */
+}
+<<< #2043
+object Day extends Enumeration {
+ type Day = Value
+
+ val
+ /** Monday */
+ MON = Value
+}
+>>>
+object Day extends Enumeration {
+ type Day = Value
+
+ val
+ /** Monday */
+ MON = Value
+}
+<<< docstring after block
+preset = default
+===
+object a {
+ input.read
+ /** foo */
+ .foo { bar }
+ /** baz */
+ .baz(qux)
+}
+>>>
+object a {
+ input.read
+ /** foo */
+ .foo { bar }
+ /** baz */
+ .baz(qux)
+}
diff --git a/scalafmt-tests/src/test/resources/test/StripMargin.stat b/scalafmt-tests/src/test/resources/test/StripMargin.stat
index 9e2a973f64..0d83ce3f7c 100644
--- a/scalafmt-tests/src/test/resources/test/StripMargin.stat
+++ b/scalafmt-tests/src/test/resources/test/StripMargin.stat
@@ -91,6 +91,22 @@ val x = s"""Formatter changed AST
?$diff
"""
.stripMargin('?')
+<<< Align | margin 1 | interpolate, different char, starts string
+val x = s"""?Formatter changed AST
+ |=====================
+ |$diff
+ ?=====================
+ ?$diff
+ """
+ .stripMargin('?')
+>>>
+val x = s"""?Formatter changed AST
+ |=====================
+ |$diff
+ ?=====================
+ ?$diff
+ """
+ .stripMargin('?')
<<< No align | margin 1 | interpolate, different char
align.stripMargin = false
===
@@ -274,3 +290,21 @@ object a {
\ updated_at = now()
\ where id in (${audienceIds.mkString(",")})""".stripMargin('\\')
}
+<<< #2025 1
+final class MyClass {
+ println(s"${1}".stripMargin)
+}
+>>>
+final class MyClass {
+ println(s"${1}".stripMargin)
+}
+<<< #2025 2
+final class MyClass {
+ println(s"""${1}
+ """.stripMargin)
+}
+>>>
+final class MyClass {
+ println(s"""${1}
+ """.stripMargin)
+}
diff --git a/scalafmt-tests/src/test/resources/test/includeNoParensInSelectChains.stat b/scalafmt-tests/src/test/resources/test/includeNoParensInSelectChains.stat
index 9ea14ab5da..a09c8b894e 100644
--- a/scalafmt-tests/src/test/resources/test/includeNoParensInSelectChains.stat
+++ b/scalafmt-tests/src/test/resources/test/includeNoParensInSelectChains.stat
@@ -4,6 +4,16 @@ List(1).toIterator.buffered
.map(_ + 2)
.filter(_ > 2)
>>>
+List(1)
+ .toIterator
+ .buffered
+ .map(_ + 2)
+ .filter(_ > 2)
+<<< include applications without parens in select chains 2
+List(1)
+ .toIterator.buffered
+ .map(_ + 2).filter(_ > 2)
+>>>
List(1)
.toIterator
.buffered
diff --git a/scalafmt-tests/src/test/resources/unit/Annotations.stat b/scalafmt-tests/src/test/resources/unit/Annotations.stat
index c522954a20..2d728b40a4 100644
--- a/scalafmt-tests/src/test/resources/unit/Annotations.stat
+++ b/scalafmt-tests/src/test/resources/unit/Annotations.stat
@@ -56,3 +56,22 @@ private trait Iterator {
+trait Invariant[F[_]] { self =>
>>>
x
+<<< #1640
+maxColumn = 120
+preset = intellij
+danglingParentheses.preset = true
+assumeStandardLibraryStripMargin = true
+===
+object a {
+@implicitNotFound("""Could not find an implicit Show for type ${T}, predicate ${P} and result ${R}.
+ | You may want to define it as an implicit function that is polymorphic function over R.""".stripMargin)
+trait Show[T, P, R]
+}
+>>>
+object a {
+ @implicitNotFound(
+ """Could not find an implicit Show for type ${T}, predicate ${P} and result ${R}.
+ | You may want to define it as an implicit function that is polymorphic function over R.""".stripMargin
+ )
+ trait Show[T, P, R]
+}
diff --git a/scalafmt-tests/src/test/resources/unit/Comment.stat b/scalafmt-tests/src/test/resources/unit/Comment.stat
index 100d27dd9c..ef1e6da139 100644
--- a/scalafmt-tests/src/test/resources/unit/Comment.stat
+++ b/scalafmt-tests/src/test/resources/unit/Comment.stat
@@ -201,3 +201,215 @@ object literal extends scala.Dynamic { // scalastyle:ignore
object literal extends scala.Dynamic { // scalastyle:ignore
???
}
+<<< #1234 1: single-line
+comments.wrap = standalone
+===
+// A really long comment that scalafmt should break up but does not because this feature is not implemented yet
+// short comment
+object a {
+ // short comment
+ // Another long comment demonstrating that wrapped comments will be aligned sensibly
+ def a = {}
+}
+>>>
+/* A really long comment that scalafmt
+ * should break up but does not because
+ * this feature is not implemented yet */
+// short comment
+object a {
+ // short comment
+ /* Another long comment demonstrating
+ * that wrapped comments will be
+ * aligned sensibly */
+ def a = {}
+}
+<<< #1234 2: multi-line
+comments.wrap = standalone
+===
+/* Block-style comments should also be wrapped, because some people like them and we want to be inclusive */
+object a {
+ /* And block-style comments should also be indented aligned sensibly, because they're just as good as single-line comments */
+ def a = {}
+}
+>>>
+/* Block-style comments should also be
+ * wrapped, because some people like
+ * them and we want to be inclusive */
+object a {
+ /* And block-style comments should
+ * also be indented aligned sensibly,
+ * because they're just as good as
+ * single-line comments */
+ def a = {}
+}
+<<< #1234 3: both
+comments.wrap = standalone
+===
+// A really long comment that scalafmt should break up but does not because this feature is not implemented yet
+// short comment
+/* short comment */
+/* Block-style comments should also be wrapped, because some people like them and we want to be inclusive */
+object a {
+ /* And block-style comments should also be indented aligned sensibly, because they're just as good as single-line comments */
+ /* long comment, with a nested comment following; shouldn't be rewritten
+ /* nested comment
+ */ */
+ /* section 1
+ *
+ * section 2
+
+
+ * section 3:
+ * paragraph 3.1.
+ * paragraph 3.2!
+ * paragraph 3.2?
+ *
+ * section 4
+ * 1. item1
+ * 10: item10
+ * - item1
+ * * item2
+ @ item3
+ */
+ def a = {} /* And block-style comments should also be indented aligned sensibly, because they're just as good as single-line comments */
+}
+>>>
+/* A really long comment that scalafmt
+ * should break up but does not because
+ * this feature is not implemented yet */
+// short comment
+/* short comment */
+/* Block-style comments should also be
+ * wrapped, because some people like
+ * them and we want to be inclusive */
+object a {
+ /* And block-style comments should
+ * also be indented aligned sensibly,
+ * because they're just as good as
+ * single-line comments */
+ /* long comment, with a nested comment following; shouldn't be rewritten
+ /* nested comment
+ */ */
+ /* section 1
+ *
+ * section 2
+ *
+ * section 3:
+ * paragraph 3.1.
+ * paragraph 3.2!
+ * paragraph 3.2?
+ *
+ * section 4
+ * 1. item1
+ * 10: item10
+ * - item1
+ * * item2
+ * @ item3 */
+ def a = {} /* And block-style comments should also be indented aligned sensibly, because they're just as good as single-line comments */
+}
+<<< #1234 4: trailing
+comments.wrap = trailing
+===
+object a {
+ def a = {} /* And block-style comments should also be indented aligned sensibly, because they're just as good as single-line comments */
+}
+>>>
+object a {
+ def a = {} /* And block-style
+ * comments should also be indented
+ * aligned sensibly, because they're
+ * just as good as single-line
+ * comments */
+}
+<<< #1234 5: trailing
+comments.wrap = trailing
+===
+object a {
+ def a = b(c) // A really long comment that scalafmt should break up but does not because this feature is not implemented yet
+}
+>>>
+object a {
+ def a = b(c) /* A really long comment
+ * that scalafmt should break up but
+ * does not because this feature is
+ * not implemented yet */
+}
+<<< #1234 6: trailing
+comments.wrap = trailing
+===
+object a {
+ val a = b(c) // A really long comment that scalafmt should break up but does not because this feature is not implemented yet
+}
+>>>
+object a {
+ val a = b(c) /* A really long comment
+ * that scalafmt should break up but
+ * does not because this feature is
+ * not implemented yet */
+}
+<<< #1234 7: trailing, use slc
+comments.wrap = trailing
+comments.wrapStandaloneSlcAsSlc = true
+===
+// A really long comment that scalafmt should break up but does not because this feature is not implemented yet
+object a {
+ val a = b(c) // A really long comment that scalafmt should break up but does not because this feature is not implemented yet
+}
+>>>
+// A really long comment that scalafmt
+// should break up but does not because
+// this feature is not implemented yet
+object a {
+ val a = b(c) /* A really long comment
+ * that scalafmt should break up but
+ * does not because this feature is
+ * not implemented yet */
+}
+<<< wrap with empty first line 1
+comments.wrap = trailing
+===
+object a {
+ /*
+ * foo bar
+ */
+}
+>>>
+object a {
+ /* foo bar */
+}
+<<< wrap with empty first line 2
+comments.wrap = trailing
+===
+object a {
+ /*
+ * foo bar */
+}
+>>>
+object a {
+ /* foo bar */
+}
+<<< #2043
+object a {
+ val
+ MON,
+ /*
+ * Tuesday
+ */
+ TUE,
+ /*
+ * Wednesday
+ */
+ WED = Value
+}
+>>>
+object a {
+ val MON,
+ /*
+ * Tuesday
+ */
+ TUE,
+ /*
+ * Wednesday
+ */
+ WED = Value
+}
diff --git a/scalafmt-tests/src/test/resources/unit/Dialect.stat b/scalafmt-tests/src/test/resources/unit/Dialect.stat
index 6c8c4864ef..2e6d3d785a 100644
--- a/scalafmt-tests/src/test/resources/unit/Dialect.stat
+++ b/scalafmt-tests/src/test/resources/unit/Dialect.stat
@@ -37,3 +37,11 @@ val x = 1_000_000
def foo(implicit x: => Int) = x
>>>
def foo(implicit x: => Int) = x
+<<< #2071 given not kw
+object Main {
+ val given = 42
+}
+>>>
+object Main {
+ val given = 42
+}
diff --git a/scalafmt-tests/src/test/resources/unit/Package.source b/scalafmt-tests/src/test/resources/unit/Package.source
index df72bb95d0..6e026c04ca 100644
--- a/scalafmt-tests/src/test/resources/unit/Package.source
+++ b/scalafmt-tests/src/test/resources/unit/Package.source
@@ -52,3 +52,37 @@ package foo
abstract class Dsl
class A extends Dsl
class B extends Dsl
+<<< format comment with single asterisk, SpaceAsterisk
+docstrings.style = SpaceAsterisk
+===
+/** Align by
+ * second asterisk.
+ *
+ */
+>>>
+/** Align by
+ * second asterisk.
+ */
+<<< format comment with single asterisk, AsteriskSpace
+docstrings.style = AsteriskSpace
+===
+/** Align space by
+ * second asterisk.
+ *
+ */
+>>>
+/** Align space by
+ * second asterisk.
+ */
+<<< format comment with single asterisk, Asterisk
+docstrings.style = Asterisk
+===
+/** Align by
+ * first asterisk.
+ *
+ */
+>>>
+/**
+ * Align by
+ * first asterisk.
+ */
diff --git a/scalafmt-tests/src/test/resources/unit/TermApply.stat b/scalafmt-tests/src/test/resources/unit/TermApply.stat
index ce6a707570..0e21252090 100644
--- a/scalafmt-tests/src/test/resources/unit/TermApply.stat
+++ b/scalafmt-tests/src/test/resources/unit/TermApply.stat
@@ -107,15 +107,15 @@ Seq(
Split(Newline, 1)
)
<<< config style with inline comment
+newlines.avoidForSimpleOverflow = [toolong]
+===
Seq(
Split(Space, 0), // End files with trailing newline
Split(Newline, 1)
)
>>>
Seq(
- Split(Space,
- 0
- ), // End files with trailing newline
+ Split(Space, 0), // End files with trailing newline
Split(Newline, 1)
)
<<< seq to var arg, #178
diff --git a/scalafmt-tests/src/test/resources/unit/Xml.stat b/scalafmt-tests/src/test/resources/unit/Xml.stat
index ab95e882d3..0a365b293c 100644
--- a/scalafmt-tests/src/test/resources/unit/Xml.stat
+++ b/scalafmt-tests/src/test/resources/unit/Xml.stat
@@ -29,3 +29,275 @@
x match { case { _* } => }
>>>
x match { case {_*} => }
+<<< #1882 1
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+ {
+ 1 + 2 + 3
+ }
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+ {
+ 1 + 2 + 3
+ }
+
+ }
+}
+<<< #1882 1 unformatted
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+ {
+ 1 + 2 + 3
+ }
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+ {
+ 1 + 2 + 3
+ }
+
+ }
+}
+<<< #1882 2
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+
+ }
+}
+<<< #1882 2 unformatted
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+ }
+}
+<<< #1882 3
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+
+ }
+}
+<<< #1882 3 unformatted
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+ {
+ 1 + 2 + 3
+ }
+
+
+ }
+}
+<<< #1882 4
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+<<< #1882 4 unformatted
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+<<< #1882 5
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+ { (1 + 2 + 3).toString("some long format") }
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+ {
+ (1 + 2 + 3).toString(
+ "some long format")
+ }
+
+ }
+}
+<<< #1882 5 unformatted
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+ { (1 + 2 + 3).toString("some long format") }
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+ {
+ (1 + 2 + 3).toString(
+ "some long format")
+ }
+
+ }
+}
+<<< #1882 6
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+<<< #1882 6 unformatted
+xmlLiterals.assumeFormatted = true
+===
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
+>>>
+object Example2 {
+ def apply() = {
+
+
+
+ }
+}
diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat b/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat
index 39d1140f28..e7221eb240 100644
--- a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat
+++ b/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat
@@ -39,3 +39,21 @@ object Test {
}
}
+<<< #2044
+verticalMultiline.excludeDanglingParens = []
+===
+object a {
+ final class Dummy[F[+_, +_]: BIOMonad: BIOPrimitives: Clock2](
+ log: LogBIO[F]
+ ) extends DIResource.LiftF(
+ F.mkRef(Map.empty[Key, Item]).map(new Dummy.Impl(_))
+ )
+}
+>>>
+object a {
+ final class Dummy[F[+_, +_]: BIOMonad: BIOPrimitives: Clock2](
+ log: LogBIO[F]
+ ) extends DIResource.LiftF(
+ F.mkRef(Map.empty[Key, Item]).map(new Dummy.Impl(_))
+ )
+}
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/CommentTest.scala b/scalafmt-tests/src/test/scala/org/scalafmt/CommentTest.scala
index 3792f7a7fd..c15b20e5ad 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/CommentTest.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/CommentTest.scala
@@ -5,8 +5,12 @@ import org.scalafmt.config.{Docstrings, ScalafmtConfig}
import org.scalafmt.util.DiffAssertions
class CommentTest extends AnyFunSuite with DiffAssertions {
- val javadocStyle: ScalafmtConfig =
- ScalafmtConfig.default.copy(docstrings = Docstrings.JavaDoc)
+
+ private val javadocStyle: ScalafmtConfig =
+ ScalafmtConfig.default.copy(docstrings =
+ ScalafmtConfig.default.docstrings.copy(style = Some(Docstrings.Asterisk))
+ )
+
test("remove trailing space in comments") {
val trailingSpace = " "
val original = s"""object a {
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/Debug.scala b/scalafmt-tests/src/test/scala/org/scalafmt/Debug.scala
index 32b5753a8e..7605ac53a1 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/Debug.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/Debug.scala
@@ -83,7 +83,7 @@ class Debug(val verbose: Boolean) {
val clean = LoggerOps.cleanup(tok).slice(0, 15).formatted("%-15s")
stack.prepend(
s"${tok.end.formatted(posWidth)}: $clean" +
- s" ${state.split} ${prev.indentation} ${prev.column} [${prev.cost}]"
+ s" ${state.split} ${prev.indentation} ${prev.column} [${state.cost}]"
)
iter(prev)
}
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/FormatTests.scala b/scalafmt-tests/src/test/scala/org/scalafmt/FormatTests.scala
index aff743681f..48e02e828b 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/FormatTests.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/FormatTests.scala
@@ -43,13 +43,13 @@ class FormatTests
implicit val loc: Position = t.loc
val debug = new Debug(onlyOne)
val runner = t.style.runner.copy(parser = parse)
- val formatted = Scalafmt.formatCode(
+ val result = Scalafmt.formatCode(
t.original,
t.style.copy(runner = scalafmtRunner(runner, debug)),
- filename = loc.filePathname
+ filename = t.filename
)
debug.printTest()
- val obtained = formatted match {
+ val obtained = result.formatted match {
case Formatted.Failure(e)
if t.style.onTestFailure.nonEmpty &&
e.getMessage.contains(t.style.onTestFailure) =>
@@ -68,7 +68,7 @@ class FormatTests
) {
assertFormatPreservesAst(t.original, obtained)(
parse,
- t.style.runner.dialect
+ result.config.runner.dialect
)
}
val debug2 = new Debug(onlyOne)
@@ -76,7 +76,7 @@ class FormatTests
.formatCode(
obtained,
t.style.copy(runner = scalafmtRunner(runner, debug2)),
- filename = loc.filePathname
+ filename = t.filename
)
.get
debug2.printTest()
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ManualTests.scala b/scalafmt-tests/src/test/scala/org/scalafmt/ManualTests.scala
index 06fcf85da2..8184cc8313 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/ManualTests.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/ManualTests.scala
@@ -22,6 +22,7 @@ object ManualTests extends HasTests {
val original = readFile(testPath)
val testFile = testPath.stripPrefix(testPrefix)
DiffTest(
+ testFile,
testFile,
new Position(testFile, testPath, 1),
original,
@@ -38,6 +39,7 @@ object ManualTests extends HasTests {
} yield {
val content = readFile(filename)
DiffTest(
+ filename,
filename,
new Position(filename, filename, 1),
content,
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala b/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala
index 35790506f9..1f67541149 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala
@@ -46,19 +46,23 @@ class ScalafmtConfigTest extends AnyFunSuite {
}
test("align preset no override") {
- val config = Scalafmt.parseHoconConfig("""
- |align = none
- |align.stripMargin = true
- """.stripMargin).get
+ val config = Scalafmt
+ .parseHoconConfig("""
+ |align = none
+ |align.stripMargin = true
+ """.stripMargin)
+ .get
// none was ignored
assert(config.align == Align(stripMargin = true))
}
test("align preset with override") {
- val config = Scalafmt.parseHoconConfig("""
- |align.preset = none
- |align.stripMargin = true
- """.stripMargin).get
+ val config = Scalafmt
+ .parseHoconConfig("""
+ |align.preset = none
+ |align.stripMargin = true
+ """.stripMargin)
+ .get
assert(config.align == Align.none.copy(stripMargin = true))
}
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala b/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala
index a78a95442e..d66fc00dc7 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala
@@ -4,7 +4,7 @@ import scala.collection.mutable
import scala.meta._
import scala.meta.testkit._
-import org.scalafmt.CompatParCollections.Converters._
+import org.scalafmt.CompatCollections.ParConverters._
import org.scalafmt.config.ScalafmtConfig
import org.scalafmt.util.AbsoluteFile
import org.scalafmt.util.FileOps
@@ -50,8 +50,7 @@ class ScalafmtProps extends AnyFunSuite with FormatAssertions {
case e: Error.FormatterOutputDoesNotParse =>
List(
Observation(
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- augmentString(e.getMessage).lines.slice(1, 2).mkString(""),
+ e.getMessage.linesIterator.slice(1, 2).mkString(""),
e.line,
FormattedOutputDoesNotParse
)
@@ -59,16 +58,13 @@ class ScalafmtProps extends AnyFunSuite with FormatAssertions {
case e: Error =>
List(Observation(e.getMessage, -1, Unknown(e)))
case e: DiffFailure =>
- val line =
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- augmentString(e.obtained).lines
- .zip(augmentString(e.expected).lines)
- .takeWhile { case (a, b) => a == b }
- .length
+ val line = e.obtained.linesIterator
+ .zip(e.expected.linesIterator)
+ .takeWhile { case (a, b) => a == b }
+ .length
List(
Observation(
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- augmentString(e.diff).lines.take(3).mkString("\n"),
+ e.diff.linesIterator.take(3).mkString("\n"),
line,
NonIdempotent
)
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala b/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala
index 9c3a4bf6a1..a1cad22770 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala
@@ -467,25 +467,63 @@ trait CliTestBehavior { this: AbstractCliTest =>
val root =
string2dir(
s"""
- |/inner/file1.scala
+ |/inner1/file1.scala
|$unformatted
|/inner2/file2.scalahala
|$unformatted
- |/inner2/file3.scalahala
+ |/inner3/file1.scala
+ |$unformatted
+ |/inner3/file2.scalahala
|$unformatted""".stripMargin
)
- val inner1 = root / "inner"
+ val inner1 = root / "inner1"
val inner2 = root / "inner2"
- val full = inner2 / "file3.scalahala"
+ val inner3 = root / "inner3"
+ val full1 = inner3 / "file1.scala"
+ val full2 = inner3 / "file2.scalahala"
- runWith(
- root,
- s"""--config-str {version="$version"} $inner1 $inner2 $full"""
- )
+ val opts = Seq(
+ s"""--config-str {version="$version"}"""
+ ) ++ Seq(inner1, inner2, full1, full2)
+ runWith(root, opts.mkString(" "))
+
+ assertNoDiff(inner1 / "file1.scala", formatted)
+ assertNoDiff(inner2 / "file2.scalahala", unformatted)
+ assertNoDiff(full1, formatted)
+ assertNoDiff(full2, formatted)
+ }
+
+ test(
+ s"includeFilters are respected for full paths but NOT test for passed directories: $label"
+ ) {
+ val root =
+ string2dir(
+ s"""
+ |/inner1/file1.scala
+ |$unformatted
+ |/inner2/file2.scalahala
+ |$unformatted
+ |/inner3/file1.scala
+ |$unformatted
+ |/inner3/file2.scalahala
+ |$unformatted""".stripMargin
+ )
+ val inner1 = root / "inner1"
+ val inner2 = root / "inner2"
+ val inner3 = root / "inner3"
+ val full1 = inner3 / "file1.scala"
+ val full2 = inner3 / "file2.scalahala"
+
+ val opts = Seq(
+ "--respect-project-filters",
+ s"""--config-str {version="$version"}"""
+ ) ++ Seq(inner1, inner2, full1, full2)
+ runWith(root, opts.mkString(" "))
assertNoDiff(inner1 / "file1.scala", formatted)
assertNoDiff(inner2 / "file2.scalahala", unformatted)
- assertNoDiff(full, formatted)
+ assertNoDiff(full1, formatted)
+ assertNoDiff(full2, unformatted)
}
test(s"--config accepts absolute paths: $label") {
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala b/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala
index a442016f4e..9f67d0838b 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala
@@ -11,13 +11,14 @@ import PositionSyntax._
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
+import scala.reflect.ClassTag
import scala.{meta => m}
-import org.scalatest.funsuite.AnyFunSuite
+import org.scalatest.funsuite.AnyFunSuite
import org.scalafmt.util.DiffAssertions
class DynamicSuite extends AnyFunSuite with DiffAssertions {
- class Format(name: String) {
+ class Format(name: String, cfgFunc: ScalafmtDynamic => ScalafmtDynamic) {
val download = new ByteArrayOutputStream()
def downloadLogs: String = download.toString()
val out = new ByteArrayOutputStream()
@@ -64,17 +65,13 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
)
}
}
- var dynamic: ScalafmtDynamic = Scalafmt
- .create(this.getClass.getClassLoader)
- .withReporter(reporter)
- .withDefaultVersion(latest)
- .asInstanceOf[ScalafmtDynamic]
- def ignoreVersion(): Unit = {
- dynamic = dynamic.withRespectVersion(false)
- }
- def ignoreExcludeFilters(): Unit = {
- dynamic = dynamic.withRespectProjectFilters(false)
- }
+ val dynamic: ScalafmtDynamic = cfgFunc(
+ Scalafmt
+ .create(this.getClass.getClassLoader)
+ .withReporter(reporter)
+ .withDefaultVersion(latest)
+ .asInstanceOf[ScalafmtDynamic]
+ )
val config = Files.createTempFile("scalafmt", ".scalafmt.conf")
val filename = Paths.get(name + ".scala")
var timestamps = 100L
@@ -97,10 +94,7 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
out.toString.replaceAllLiterally(config.toString, "path/.scalafmt.conf")
}
def errors: String = {
- // work around scala/bug#11125
- scala.Predef
- .augmentString(out.toString)
- .lines
+ out.toString.linesIterator
.filter(_.startsWith("error"))
.mkString("\n")
}
@@ -138,34 +132,48 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
def assertMissingVersion()(implicit pos: Position): Unit = {
out.reset()
missingVersions.clear()
- val original = "object A"
- val obtained = dynamic.format(config, filename, original)
+ intercept[ScalafmtDynamicError.ConfigMissingVersion] {
+ dynamic.format(config, filename, "object A")
+ }
assert(out.toString().isEmpty)
assert(missingVersions.nonEmpty)
- assertNoDiff(obtained, original, "Formatter did not error")
+ }
+ def assertThrows[A <: AnyRef: ClassTag](
+ code: String = "object A { }"
+ )(implicit pos: Position): A = {
+ out.reset()
+ intercept[A] {
+ dynamic.format(config, filename, code)
+ }
}
def assertError(expected: String)(implicit pos: Position): Unit = {
assertError("object A { }", expected)
}
- def assertError(code: String, expected: String)(implicit
- pos: Position
- ): Unit = {
+ def assertError(
+ code: String,
+ expected: String,
+ path: Path = filename
+ )(implicit pos: Position): Unit = {
out.reset()
- val obtained = dynamic.format(config, filename, code)
+ val obtained = dynamic.format(config, path, code)
assertNoDiff(relevant, expected)
assertNoDiff(obtained, obtained, "Formatter did not error")
}
}
- def check(name: String)(fn: Format => Unit): Unit = {
+ def check(
+ name: String,
+ cfgFunc: ScalafmtDynamic => ScalafmtDynamic = identity
+ )(fn: Format => Unit): Unit = {
test(name) {
- val format = new Format(name)
+ val format = new Format(name, cfgFunc)
try fn(format)
finally format.dynamic.clear()
}
}
private val testedVersions = Seq(
+ "2.5.3",
"2.0.0-RC4",
"1.6.0-RC4",
"1.5.1",
@@ -181,14 +189,14 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
def checkExhaustive(name: String)(fn: (Format, String) => Unit): Unit = {
testedVersions.foreach { version =>
test(s"$name (version: $version)") {
- val format = new Format(name)
+ val format = new Format(name, identity)
try fn(format, version)
finally format.dynamic.clear()
}
}
}
- def latest = "2.0.0-RC1"
+ def latest = "2.5.3"
def checkVersion(version: String): Unit = {
check(s"v$version") { f =>
@@ -206,7 +214,8 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
//checkVersion("0.2.8") // fails for now
check("parse-error") { f =>
- def check(): Unit = {
+ def check(version: String): Unit = {
+ f.setVersion(version)
f.assertError(
"object object A",
"""|parse-error.scala:1:8: error: identifier expected but object found
@@ -214,16 +223,13 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
| ^^^^^^""".stripMargin
)
}
- f.setVersion(latest)
- check()
- f.setVersion("1.0.0")
- check()
+ check(latest)
+ check("1.0.0")
}
check("missing-version") { f => f.assertMissingVersion() }
- check("ignore-version") { f =>
- f.ignoreVersion()
+ check("ignore-version", _.withRespectVersion(false)) { f =>
f.assertFormat(
"object A { }",
"object A {}\n"
@@ -241,19 +247,17 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
|]
|""".stripMargin
)
- def check(): Unit = {
+ def check(version: String): Unit = {
+ f.setVersion(version)
f.assertNotIgnored("path/FooSpec.scala")
f.assertIgnored("path/App.scala")
f.assertIgnored("path/UserSpec.scala")
}
- f.setVersion(latest)
- check()
- f.setVersion("1.0.0")
- check()
- f.ignoreExcludeFilters()
+ check(latest)
+ check("1.0.0")
}
- check("ignore-exclude-filters") { f =>
+ check("ignore-exclude-filters", _.withRespectProjectFilters(false)) { f =>
f.setConfig(
"""
|project.includeFilters = [
@@ -264,14 +268,13 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
|]
|""".stripMargin
)
- def check(): Unit = {
+ def check(version: String): Unit = {
+ f.setVersion(version)
f.assertNotIgnored("path/App.pm")
f.assertNotIgnored("path/App.scala")
f.assertNotIgnored("path/UserSpec.scala")
}
- f.setVersion(latest)
- f.ignoreExcludeFilters()
- check()
+ check(latest)
}
check("config-error") { f =>
@@ -280,8 +283,8 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
|version=$latest
|""".stripMargin
)
- f.assertError(
- """|error: path/.scalafmt.conf: Invalid field: max. Expected one of version, maxColumn, docstrings, optIn, binPack, continuationIndent, align, spaces, literals, lineEndings, rewriteTokens, rewrite, indentOperator, newlines, runner, indentYieldKeyword, importSelectors, unindentTopLevelOperators, includeCurlyBraceInSelectChains, includeNoParensInSelectChains, assumeStandardLibraryStripMargin, danglingParentheses, poorMansTrailingCommasInConfigStyle, trailingCommas, verticalMultilineAtDefinitionSite, verticalMultilineAtDefinitionSiteArityThreshold, verticalMultiline, onTestFailure, encoding, project
+ f.assertThrows[ScalafmtDynamicError.ConfigParseError](
+ """|error: path/.scalafmt.conf: Invalid config: Invalid field: max. Expected one of version, maxColumn, docstrings, optIn, binPack, continuationIndent, align, spaces, literals, lineEndings, rewriteTokens, rewrite, indentOperator, newlines, runner, indentYieldKeyword, importSelectors, unindentTopLevelOperators, includeCurlyBraceInSelectChains, includeNoParensInSelectChains, assumeStandardLibraryStripMargin, danglingParentheses, poorMansTrailingCommasInConfigStyle, trailingCommas, verticalMultilineAtDefinitionSite, verticalMultilineAtDefinitionSiteArityThreshold, verticalMultiline, onTestFailure, encoding, project
|""".stripMargin
)
}
@@ -293,6 +296,13 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
assert(f.parsedCount == 1, f.parsed)
f.setConfig("invalid")
+ val parseError = f.assertThrows[ScalafmtDynamicError.ConfigParseError]()
+ assert(
+ parseError.getMessage
+ .contains("Key 'invalid' may not be followed by token: end of file")
+ )
+
+ f.setConfig("maxColumn = 40")
f.assertMissingVersion()
f.setConfig(
@@ -320,49 +330,70 @@ class DynamicSuite extends AnyFunSuite with DiffAssertions {
check("wrong-version") { f =>
f.setVersion("1.0")
- f.assertError(
- """|error: path/.scalafmt.conf: org.scalafmt.dynamic.exceptions.ScalafmtException: failed to resolve Scalafmt version '1.0'
- |Caused by: org.scalafmt.dynamic.ScalafmtVersion$InvalidVersionException: Invalid scalafmt version 1.0
- |""".stripMargin
- )
+ val error = f.assertThrows[ScalafmtDynamicError.ConfigInvalidVersion]()
+ assert(error.getMessage == "Invalid version: 1.0")
assert(f.downloadLogs.isEmpty)
}
check("sbt") { f =>
- def check(): Unit = {
+ def check(version: String): Unit = {
+ f.setVersion(version)
List("build.sbt", "build.sc").foreach { filename =>
+ val path = Paths.get(filename)
+ // test sbt allows top-level terms
f.assertFormat(
"lazy val x = project",
"lazy val x = project\n",
- Paths.get(filename)
+ path
)
+ // test scala doesn't allow top-level terms (not passing path here)
f.assertError(
"lazy val x = project",
"""|sbt.scala:1:1: error: classes cannot be lazy
|lazy val x = project
|^^^^""".stripMargin
)
+ // check wrapped literals, supported in sbt using scala 2.13+
+ val wrappedLiteral = "object a { val x: Option[0] = Some(0) }"
+ def isWrappedLiteralFailure: Unit =
+ f.assertError(
+ wrappedLiteral,
+ s"""$filename:1:28: error: identifier expected but integer constant found
+ |$wrappedLiteral
+ | ^""".stripMargin,
+ path
+ )
+ def isWrappedLiteralSuccess: Unit =
+ f.assertFormat(
+ wrappedLiteral,
+ wrappedLiteral.replaceAll(" +", " ").trim + "\n",
+ path
+ )
+ if (version > "2.0")
+ isWrappedLiteralSuccess
+ else
+ isWrappedLiteralFailure
}
}
f.setConfig(
"""|project.includeFilters = [ ".*" ]
|""".stripMargin
)
- f.setVersion(latest)
- check()
- f.setVersion("1.2.0")
- check()
+ check(latest)
+ check("1.2.0")
}
check("no-config") { f =>
Files.delete(f.config)
- f.assertError("""|error: path/.scalafmt.conf: file does not exist
- |""".stripMargin)
+ f.assertThrows[ScalafmtDynamicError.ConfigDoesNotExist](
+ """|error: path/.scalafmt.conf: Missing config
+ |""".stripMargin
+ )
}
check("intellij-default-config") { f: Format =>
- val version = "1.5.1"
- f.setVersion(version)
+ val version = ScalafmtVersion(1, 5, 1)
+ f.setVersion(version.toString)
f.assertFormat()
val reflect = f.dynamic.formatCache.getFromCache(version)
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala b/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala
index 03638f8fb8..a2466aeb8d 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala
@@ -1,99 +1,66 @@
package org.scalafmt.dynamic
-import org.scalafmt.dynamic.ScalafmtVersion.InvalidVersionException
import org.scalatest.funsuite.AnyFunSuite
class ScalafmtVersionSuite extends AnyFunSuite {
test("parse valid versions") {
assert(
- ScalafmtVersion.parse("2.0.0") == Right(ScalafmtVersion(2, 0, 0, 0))
+ ScalafmtVersion.parse("2.0.0") == Some(ScalafmtVersion(2, 0, 0))
)
assert(
- ScalafmtVersion.parse("0.1.3") == Right(ScalafmtVersion(0, 1, 3, 0))
+ ScalafmtVersion.parse("0.1.3") == Some(ScalafmtVersion(0, 1, 3))
)
assert(
- ScalafmtVersion.parse("2.0.0-RC4") == Right(ScalafmtVersion(2, 0, 0, 4))
+ ScalafmtVersion.parse("2.0.0-RC4") == Some(ScalafmtVersion(2, 0, 0, 4))
)
assert(
- ScalafmtVersion.parse("2.1.1") == Right(ScalafmtVersion(2, 1, 1, 0))
+ ScalafmtVersion.parse("2.1.1") == Some(ScalafmtVersion(2, 1, 1))
)
assert(
- ScalafmtVersion
- .parse("2.2.3-SNAPSHOT") == Right(ScalafmtVersion(2, 2, 3, 0, true))
+ ScalafmtVersion.parse("2.2.3-SNAPSHOT") ==
+ Some(ScalafmtVersion(2, 2, 3, 0, true))
)
assert(
- ScalafmtVersion.parse("2.0.0-RC1-SNAPSHOT") == Right(
- ScalafmtVersion(2, 0, 0, 1, true)
- )
+ ScalafmtVersion.parse("2.0.0-RC1-SNAPSHOT") ==
+ Some(ScalafmtVersion(2, 0, 0, 1, true))
)
assert(
- ScalafmtVersion.parse("2.2.2-SNAPSHOT") == Right(
- ScalafmtVersion(2, 2, 2, 0, true)
- )
+ ScalafmtVersion.parse("2.2.2-SNAPSHOT") ==
+ Some(ScalafmtVersion(2, 2, 2, 0, true))
)
}
test("toString") {
- assert(ScalafmtVersion.parse("2.2.2-RC2").right.get.toString == "2.2.2-RC2")
- assert(ScalafmtVersion.parse("2.2.2").right.get.toString == "2.2.2")
+ assert(ScalafmtVersion.parse("2.2.2-RC2").get.toString == "2.2.2-RC2")
+ assert(ScalafmtVersion.parse("2.2.2").get.toString == "2.2.2")
assert(
ScalafmtVersion
.parse("2.2.2-SNAPSHOT")
- .right
.get
.toString == "2.2.2-SNAPSHOT"
)
assert(
ScalafmtVersion
.parse("2.2.2-RC2-SNAPSHOT")
- .right
.get
.toString == "2.2.2-RC2-SNAPSHOT"
)
}
test("fail on invalid versions") {
- assert(ScalafmtVersion.parse("2.0") == Left(InvalidVersionException("2.0")))
- assert(
- ScalafmtVersion.parse("v2.0.0") == Left(InvalidVersionException("v2.0.0"))
- )
- assert(ScalafmtVersion.parse("avs") == Left(InvalidVersionException("avs")))
- assert(
- ScalafmtVersion
- .parse("1.2.3-M14") == Left(InvalidVersionException("1.2.3-M14"))
- )
- assert(
- ScalafmtVersion
- .parse("1.1.1.1") == Left(InvalidVersionException("1.1.1.1"))
- )
- assert(
- ScalafmtVersion.parse("2.-1.0") == Left(InvalidVersionException("2.-1.0"))
- )
- assert(
- ScalafmtVersion.parse("2.1.0.") == Left(InvalidVersionException("2.1.0."))
- )
- assert(
- ScalafmtVersion.parse(",2.1.0") == Left(InvalidVersionException(",2.1.0"))
- )
- assert(
- ScalafmtVersion.parse("2.1a.0") == Left(InvalidVersionException("2.1a.0"))
- )
- assert(
- ScalafmtVersion.parse("2.1.0-") == Left(InvalidVersionException("2.1.0-"))
- )
- assert(
- ScalafmtVersion
- .parse("2.1.0-rc1") == Left(InvalidVersionException("2.1.0-rc1"))
- )
- assert(
- ScalafmtVersion
- .parse("2.1.0-RC1-M4") == Left(InvalidVersionException("2.1.0-RC1-M4"))
- )
- assert(
- ScalafmtVersion.parse("2.0.0-RC1+metadata") == Left(
- InvalidVersionException("2.0.0-RC1+metadata")
- )
- )
+ assert(ScalafmtVersion.parse("2.0") eq None)
+ assert(ScalafmtVersion.parse("v2.0.0") eq None)
+ assert(ScalafmtVersion.parse("avs") eq None)
+ assert(ScalafmtVersion.parse("1.2.3-M14") eq None)
+ assert(ScalafmtVersion.parse("1.1.1.1") eq None)
+ assert(ScalafmtVersion.parse("2.-1.0") eq None)
+ assert(ScalafmtVersion.parse("2.1.0.") eq None)
+ assert(ScalafmtVersion.parse(",2.1.0") eq None)
+ assert(ScalafmtVersion.parse("2.1a.0") eq None)
+ assert(ScalafmtVersion.parse("2.1.0-") eq None)
+ assert(ScalafmtVersion.parse("2.1.0-rc1") eq None)
+ assert(ScalafmtVersion.parse("2.1.0-RC1-M4") eq None)
+ assert(ScalafmtVersion.parse("2.0.0-RC1+metadata") eq None)
}
test("order versions") {
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/DiffTest.scala b/scalafmt-tests/src/test/scala/org/scalafmt/util/DiffTest.scala
index f7b01cf5bb..3879c31d2b 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/util/DiffTest.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/util/DiffTest.scala
@@ -5,6 +5,7 @@ import org.scalafmt.config.ScalafmtConfig
case class DiffTest(
name: String,
+ filename: String,
loc: Position,
original: String,
expected: String,
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatAssertions.scala b/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatAssertions.scala
index 97c0411fbb..8f3be3a837 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatAssertions.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatAssertions.scala
@@ -54,13 +54,11 @@ trait FormatAssertions extends DiffAssertions {
*/
def diffAsts(original: String, obtained: String): String = {
// compareContents(formatAst(original), formatAst(obtained))
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- augmentString(
- compareContents(
- original.replace("(", "\n("),
- obtained.replace("(", "\n(")
- )
- ).lines.mkString("\n")
+
+ compareContents(
+ original.replace("(", "\n("),
+ obtained.replace("(", "\n(")
+ ).linesIterator.mkString("\n")
}
// TODO(olafur) move this to scala.meta?
@@ -68,8 +66,7 @@ trait FormatAssertions extends DiffAssertions {
def parseException2Message(e: ParseException, obtained: String): String = {
val range = 3
val i = e.pos.startLine
- // Predef.augmentString = work around scala/bug#11125 on JDK 11
- val lines = augmentString(obtained).lines.toVector
+ val lines = obtained.linesIterator.toVector
val arrow = (" " * (e.pos.startColumn - 2)) + "^"
s"""${lines.slice(i - range, i + 1).mkString("\n")}
|$arrow
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/HasTests.scala b/scalafmt-tests/src/test/scala/org/scalafmt/util/HasTests.scala
index 795c4bbc81..40a96b5e57 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/util/HasTests.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/util/HasTests.scala
@@ -1,6 +1,7 @@
package org.scalafmt.util
import java.io.File
+import java.util.regex.Pattern
import scala.annotation.tailrec
@@ -108,21 +109,26 @@ trait HasTests extends FormatAssertions {
if (idx < 0) cnt else numLines(str, cnt + 1, idx + sep.length)
}
var linenum = numLines(split.head, 2)
+ val inputOutputRegex = Pattern.compile(
+ s"(.+?)$sep(?:(.+)$sep===$sep)?(.+)$sep>>>(?: +(.+?))?$sep(.*)",
+ Pattern.DOTALL
+ )
split.tail.map { t =>
- val before :: expected :: Nil = t.split(s"$sep>>>$sep", 2).toList
- val extraConfig = before.split(s"$sep===$sep", 2).toList
- val (testStyle, name :: original :: Nil) = extraConfig match {
- case nameAndOriginal :: Nil =>
- (style, nameAndOriginal.split(sep, 2).toList)
- case nameAndConfig :: original :: Nil =>
- val name :: config :: Nil = nameAndConfig.split(sep, 2).toList
- (loadStyle(config, style), name :: original :: Nil)
- case other =>
- throw new IllegalStateException(s"invalid extraConfig: ${other}")
- }
+ val matcher = inputOutputRegex.matcher(t)
+ if (!matcher.matches())
+ throw new IllegalStateException(
+ s"invalid test, missing delimiters:\n$t"
+ )
+ val name = matcher.group(1)
+ val extraConfig = Option(matcher.group(2))
+ val original = matcher.group(3)
+ val altFilename = Option(matcher.group(4))
+ val expected = matcher.group(5)
+ val testStyle = extraConfig.fold(style)(loadStyle(_, style))
val actualName = stripPrefix(name)
val test = DiffTest(
actualName,
+ altFilename.getOrElse(filename),
new Position(spec, filename, linenum),
original,
trimmed(expected),
@@ -163,17 +169,17 @@ trait HasTests extends FormatAssertions {
implicit val loc: Position = t.loc
val debug = new Debug(false)
val runner = scalafmtRunner(t.style.runner, debug).copy(parser = parse)
- val obtained = Scalafmt
+ val result = Scalafmt
.formatCode(
t.original,
t.style.copy(runner = runner),
- filename = loc.filePathname
+ filename = t.filename
)
- .get
+ val obtained = result.formatted.get
if (t.style.rewrite.rules.isEmpty) {
assertFormatPreservesAst(t.original, obtained)(
parse,
- t.style.runner.dialect
+ result.config.runner.dialect
)
}
assertNoDiff(obtained, t.expected)
diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/Tabulator.scala b/scalafmt-tests/src/test/scala/org/scalafmt/util/Tabulator.scala
index 50d88ea114..79aec9fa56 100644
--- a/scalafmt-tests/src/test/scala/org/scalafmt/util/Tabulator.scala
+++ b/scalafmt-tests/src/test/scala/org/scalafmt/util/Tabulator.scala
@@ -25,7 +25,7 @@ object Tabulator {
def formatRows(rowSeparator: String, rows: Seq[String]): String =
(rowSeparator :: rows.head :: rowSeparator :: rows.tail.toList ::: rowSeparator :: List(
- )).mkString("\n")
+ )).mkString("\n")
def formatRow(row: Seq[Any], colSizes: Seq[Int]) = {
val cells =