/
FormatToken.scala
123 lines (104 loc) · 3.4 KB
/
FormatToken.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package org.scalafmt.internal
import scala.annotation.tailrec
import scala.meta.Tree
import scala.meta.tokens.Token
import org.scalafmt.util.TokenOps._
/** Two adjacent non-whitespace tokens.
*
* Consider a FormatToken as a node in a search graph and [[Split]] are the
* edges. The format tokens remain unchanged after formatting, while the splits
* are changed.
*
* @param left
* The left non-whitespace token.
* @param right
* The right non-whitespace token.
* @param meta
* Extra information about the token
*/
case class FormatToken(left: Token, right: Token, meta: FormatToken.Meta) {
override def toString =
s"${meta.left.text}∙${meta.right.text}[${left.end}:${right.end}]"
def inside(range: Set[Range]): Boolean = {
if (range.isEmpty) true
else range.exists(_.contains(right.pos.endLine))
}
def between = meta.between
lazy val newlinesBetween: Int = {
val nl = meta.newlinesBetween
// make sure to break before/after docstring
if (nl != 0) nl
else if (left.is[Token.Comment] && isDocstring(meta.left.text)) 1
else if (right.is[Token.Comment] && isDocstring(meta.right.text)) 1
else 0
}
@inline def noBreak: Boolean = FormatToken.noBreak(newlinesBetween)
@inline def hasBreak: Boolean = !noBreak
@inline def hasBlankLine: Boolean = FormatToken.hasBlankLine(newlinesBetween)
@inline def leftHasNewline = meta.left.hasNL
@inline def rightHasNewline = meta.right.hasNL
@inline def hasBreakOrEOF: Boolean = hasBreak || right.is[Token.EOF]
/** A format token is uniquely identified by its left token.
*/
override def hashCode(): Int = hash(left).##
private[scalafmt] def withIdx(idx: Int): FormatToken =
copy(meta = meta.copy(idx = idx))
}
object FormatToken {
@inline def noBreak(newlines: Int): Boolean = newlines == 0
@inline def hasBlankLine(newlines: Int): Boolean = newlines > 1
@inline def isNL(token: Token): Boolean = token.is[Token.LF]
/** @param between
* The whitespace tokens between left and right.
* @param idx
* The token's index in the FormatTokens array
* @param formatOff
* if true, between and right should not be formatted
*/
case class Meta(
between: Array[Token],
idx: Int,
formatOff: Boolean,
left: TokenMeta,
right: TokenMeta
) {
@inline def leftOwner: Tree = left.owner
@inline def rightOwner: Tree = right.owner
/** returns a value between 0 and 2 (2 for a blank line) */
lazy val newlinesBetween: Int = {
@tailrec
def count(idx: Int, maxCount: Int): Int = {
if (idx == between.length) maxCount
else {
val token = between(idx)
if (isNL(token))
if (maxCount == 0) count(idx + 1, 1) else 2
else
count(idx + 1, maxCount)
}
}
count(0, 0)
}
}
case class TokenMeta(
owner: Tree,
text: String
) {
lazy val firstNL = text.indexOf('\n')
@inline def hasNL: Boolean = firstNL >= 0
def countNL: Int = {
var cnt = 0
var idx = firstNL
while (idx >= 0) {
cnt += 1
idx = text.indexOf('\n', idx + 1)
}
cnt
}
}
class ExtractFromMeta[A](f: FormatToken.Meta => Option[A]) {
def unapply(meta: FormatToken.Meta): Option[A] = f(meta)
}
val LeftOwner = new ExtractFromMeta(x => Some(x.leftOwner))
val RightOwner = new ExtractFromMeta(x => Some(x.rightOwner))
}