diff --git a/docs/configuration.md b/docs/configuration.md index 71ad889175..cb0f262309 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4127,19 +4127,22 @@ the default `maxColumn`. ### `docstrings.blankFirstLine` Controls whether to force the first line to be blank in a multiline docstring. -Keep in mind that some combinations of parameters are prohibited (e.g., -`blankFirstLine=keep` contradicts with `style=Asterisk`). +Some values of [`docstrings.style`](#docstringsstyle) might take precedence. +Takes the following values: + +- `keep`: preserves the first line as-is +- `fold`: will ensure there's no blank first line + (default; replaced `no` in v3.8.2) +- `unfold`: will enforce a blank first line + (replaced `yes` in v3.8.2) > Since v2.7.5. Ignored for `docstrings.style = keep` or +> `docstrings.style = Asterisk` or > `docstrings.wrap = no`. -```scala mdoc:defaults -docstrings.blankFirstLine -``` - ```scala mdoc:scalafmt # do not force a blank first line -docstrings.blankFirstLine = no +docstrings.blankFirstLine = fold docstrings.style = SpaceAsterisk maxColumn = 30 --- @@ -4154,7 +4157,7 @@ val a = 1 ```scala mdoc:scalafmt # force a blank first line -docstrings.blankFirstLine = yes +docstrings.blankFirstLine = unfold docstrings.style = SpaceAsterisk maxColumn = 30 --- 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 0b60692369..bdfde87744 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,9 +1,5 @@ package org.scalafmt.config -import org.scalafmt.util.ValidationOps - -import scala.collection.mutable - import metaconfig._ /** @param oneline @@ -26,7 +22,7 @@ case class Docstrings( wrap: Docstrings.Wrap = Docstrings.Wrap.yes, private[config] val wrapMaxColumn: Option[Int] = None, forceBlankLineBefore: Option[Boolean] = None, - blankFirstLine: Docstrings.BlankFirstLine = Docstrings.BlankFirstLine.no, + blankFirstLine: Option[Docstrings.BlankFirstLine] = None, style: Docstrings.Style = Docstrings.SpaceAsterisk, ) { import Docstrings._ @@ -34,19 +30,12 @@ case class Docstrings( def withoutRewrites: Docstrings = copy(removeEmpty = false, wrap = Wrap.no, style = Preserve) - def skipFirstLineIf(wasBlank: Boolean): Boolean = blankFirstLine match { - case BlankFirstLine.yes => true - case BlankFirstLine.no => style.skipFirstLine - case BlankFirstLine.keep => wasBlank || style.skipFirstLine - } - - def validate(implicit errors: mutable.Buffer[String]): Unit = { - import ValidationOps._ - addIf( - blankFirstLine.eq(BlankFirstLine.keep) && style.eq(Docstrings.Asterisk), - s"docstrings", - ) - } + def skipFirstLineIf(wasBlank: Boolean): Boolean = style.skipFirstLine + .orElse(blankFirstLine).exists { + case BlankFirstLine.unfold => true + case BlankFirstLine.fold => false + case BlankFirstLine.keep => wasBlank + } } @@ -58,21 +47,21 @@ object Docstrings { .deriveCodecEx(Docstrings()).noTypos sealed abstract class Style { - def skipFirstLine: Boolean + def skipFirstLine: Option[BlankFirstLine] } case object Preserve extends Style { - override def skipFirstLine: Boolean = throw new NotImplementedError( - "skipFirstLine called for docstrings.style=preserve, it's a bug in scalafmt", - ) + override def skipFirstLine: Option[BlankFirstLine] = + Some(BlankFirstLine.keep) } case object Asterisk extends Style { - override def skipFirstLine: Boolean = true + override def skipFirstLine: Option[BlankFirstLine] = + Some(BlankFirstLine.unfold) } case object SpaceAsterisk extends Style { - override def skipFirstLine: Boolean = false + override def skipFirstLine: Option[BlankFirstLine] = None } case object AsteriskSpace extends Style { - override def skipFirstLine: Boolean = false + override def skipFirstLine: Option[BlankFirstLine] = None } implicit val reader: ConfCodecEx[Style] = ReaderUtil @@ -102,13 +91,15 @@ object Docstrings { sealed abstract class BlankFirstLine object BlankFirstLine { - case object yes extends BlankFirstLine - case object no extends BlankFirstLine + case object unfold extends BlankFirstLine + case object fold extends BlankFirstLine case object keep extends BlankFirstLine implicit val codec: ConfCodecEx[BlankFirstLine] = ReaderUtil - .oneOfCustom[BlankFirstLine](yes, no, keep) { - case Conf.Bool(true) => Configured.Ok(yes) - case Conf.Bool(false) => Configured.Ok(no) + .oneOfCustom[BlankFirstLine](unfold, fold, keep) { + case Conf.Str("no") => Configured.Ok(fold) + case Conf.Bool(false) => Configured.Ok(fold) + case Conf.Str("yes") => Configured.Ok(unfold) + case Conf.Bool(true) => Configured.Ok(unfold) } } 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 5f6fcc6da9..f425b63c17 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 @@ -388,7 +388,6 @@ object ScalafmtConfig { addIf(optIn.breaksInsideChains) addIf(!includeCurlyBraceInSelectChains) } - docstrings.validate if (errors.nonEmpty) { allErrors += s"newlines.source=${newlines.source} and [" errors.foreach(x => allErrors += "\t" + x)