Skip to content

Commit

Permalink
State: fix column positions for multiline strings
Browse files Browse the repository at this point in the history
  • Loading branch information
kitbellew committed Jun 9, 2020
1 parent 4d4a157 commit b84b862
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.scalafmt.internal

import java.util.regex.Matcher
import java.util.regex.Pattern

import scala.annotation.tailrec
import scala.meta.tokens.Token

Expand Down Expand Up @@ -45,28 +48,12 @@ final case class State(
}
val newIndent = newIndents.foldLeft(0)(_ + _.length)

// 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 (columnOnCurrentLine, nextStateColumn) = State.getColumns(
tok,
newIndent,
if (split.modification.isNewline) None else Some(column + split.length)
)
val newPolicy: PolicySummary = policy.combine(split.policy, tok.left.end)
val splitWithPenalty = {
if (
Expand Down Expand Up @@ -154,4 +141,71 @@ 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)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -190,29 +190,6 @@ 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.
lit.syntax.linesIterator
.map(_.replaceFirst(" *|", "").length)
.max
case _ =>
val tokenSyntax = token.syntax
val firstNewline = tokenSyntax.indexOf('\n')
if (firstNewline == -1) tokenSyntax.length
else firstNewline
}

val formatOffCode = Set(
"// @formatter:off", // IntelliJ
"// format: off" // scalariform
Expand Down
8 changes: 2 additions & 6 deletions scalafmt-tests/src/test/resources/default/Apply.stat
Original file line number Diff line number Diff line change
Expand Up @@ -772,14 +772,10 @@ optIn.configStyleArguments = true
>>>
commonConfig(
debugConfig(on = false)
.withFallback(
ConfigFactory.parseString(
"""
.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)
)
<<< #1800
Expand Down
3 changes: 1 addition & 2 deletions scalafmt-tests/src/test/resources/default/String.stat
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ val x = """Short line
|Long line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
"""
>>>
val x =
"""Short line
val x = """Short line
|Long line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
"""
<<< column limit 2
Expand Down
7 changes: 4 additions & 3 deletions scalafmt-tests/src/test/resources/unit/Annotations.stat
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ object a {
trait Show[T, P, R]
}
>>>
Idempotency violated
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)
@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]
}

0 comments on commit b84b862

Please sign in to comment.