@@ -16,8 +16,6 @@ import scala.annotation.switch
16
16
import scala .collection .mutable
17
17
18
18
trait MessageRendering {
19
- import Highlight .*
20
- import Offsets .*
21
19
22
20
/** Remove ANSI coloring from `str`, useful for getting real length of
23
21
* strings
@@ -27,25 +25,31 @@ trait MessageRendering {
27
25
def stripColor (str : String ): String =
28
26
str.replaceAll(" \u001b\\ [.*?m" , " " )
29
27
30
- /** List of all the inline calls that surround the position */
31
- def inlinePosStack (pos : SourcePosition ): List [SourcePosition ] =
32
- if pos.outer != null && pos.outer.exists then pos :: inlinePosStack(pos.outer)
28
+ /** When inlining a method call, if there's an error we'd like to get the
29
+ * outer context and the `pos` at which the call was inlined.
30
+ *
31
+ * @return a list of strings with inline locations
32
+ */
33
+ def outer (pos : SourcePosition , prefix : String )(using Context ): List [String ] =
34
+ if (pos.outer.exists)
35
+ i " $prefix| This location contains code that was inlined from $pos" ::
36
+ outer(pos.outer, prefix)
33
37
else Nil
34
38
35
39
/** Get the sourcelines before and after the position, as well as the offset
36
40
* for rendering line numbers
37
41
*
38
42
* @return (lines before error, lines after error, line numbers offset)
39
43
*/
40
- private def sourceLines (pos : SourcePosition )(using Context , Level , Offset ): (List [String ], List [String ], Int ) = {
44
+ def sourceLines (pos : SourcePosition , diagnosticLevel : String )(using Context ): (List [String ], List [String ], Int ) = {
41
45
assert(pos.exists && pos.source.file.exists)
42
46
var maxLen = Int .MinValue
43
47
def render (offsetAndLine : (Int , String )): String = {
44
- val (offset1 , line) = offsetAndLine
45
- val lineNbr = ( pos.source.offsetToLine(offset1) + 1 ).toString
46
- val prefix = String .format( s " % ${offset - 2 } s |" , lineNbr)
48
+ val (offset , line) = offsetAndLine
49
+ val lineNbr = pos.source.offsetToLine(offset)
50
+ val prefix = s " ${lineNbr + 1 } | "
47
51
maxLen = math.max(maxLen, prefix.length)
48
- val lnum = hl(" " * math.max(0 , maxLen - prefix.length - 1 ) + prefix)
52
+ val lnum = hl(diagnosticLevel)( " " * math.max(0 , maxLen - prefix.length) + prefix)
49
53
lnum + line.stripLineEnd
50
54
}
51
55
@@ -73,75 +77,23 @@ trait MessageRendering {
73
77
)
74
78
}
75
79
76
- /** Generate box containing the report title
77
- *
78
- * ```
79
- * -- Error: source.scala ---------------------
80
- * ```
81
- */
82
- private def boxTitle (title : String )(using Context , Level , Offset ): String =
83
- val pageWidth = ctx.settings.pageWidth.value
84
- val line = " -" * (pageWidth - title.length - 4 )
85
- hl(s " -- $title $line" )
86
-
87
- /** The position markers aligned under the error
88
- *
89
- * ```
90
- * | ^^^^^
91
- * ```
92
- */
93
- private def positionMarker (pos : SourcePosition )(using Context , Level , Offset ): String = {
80
+ /** The column markers aligned under the error */
81
+ def columnMarker (pos : SourcePosition , offset : Int , diagnosticLevel : String )(using Context ): String = {
82
+ val prefix = " " * (offset - 1 )
94
83
val padding = pos.startColumnPadding
95
- val carets =
84
+ val carets = hl(diagnosticLevel) {
96
85
if (pos.startLine == pos.endLine)
97
86
" ^" * math.max(1 , pos.endColumn - pos.startColumn)
98
87
else " ^"
99
- hl(s " $offsetBox$padding$carets" )
88
+ }
89
+ s " $prefix| $padding$carets"
100
90
}
101
91
102
- /** The horizontal line with the given offset
103
- *
104
- * ```
105
- * |
106
- * ```
107
- */
108
- private def offsetBox (using Context , Level , Offset ): String =
109
- val prefix = " " * (offset - 1 )
110
- hl(s " $prefix| " )
111
-
112
- /** The end of a box section
113
- *
114
- * ```
115
- * |---------------
116
- * ```
117
- * Or if there `soft` is true,
118
- * ```
119
- * |···············
120
- * ```
121
- */
122
- private def newBox (soft : Boolean = false )(using Context , Level , Offset ): String =
123
- val pageWidth = ctx.settings.pageWidth.value
124
- val prefix = " " * (offset - 1 )
125
- val line = (if soft then " ·" else " -" ) * (pageWidth - offset)
126
- hl(s " $prefix| $line" )
127
-
128
- /** The end of a box section
129
- *
130
- * ```
131
- * ·----------------
132
- * ```
133
- */
134
- private def endBox (using Context , Level , Offset ): String =
135
- val pageWidth = ctx.settings.pageWidth.value
136
- val prefix = " " * (offset - 1 )
137
- val line = " -" * (pageWidth - offset)
138
- hl(s " ${prefix}· $line" )
139
-
140
92
/** The error message (`msg`) aligned under `pos`
141
93
*
142
94
* @return aligned error message
143
95
*/
144
- private def errorMsg (pos : SourcePosition , msg : String )(using Context , Level , Offset ): String = {
96
+ def errorMsg (pos : SourcePosition , msg : String , offset : Int )(using Context ): String = {
145
97
val padding = msg.linesIterator.foldLeft(pos.startColumnPadding) { (pad, line) =>
146
98
val lineLength = stripColor(line).length
147
99
val maxPad = math.max(0 , ctx.settings.pageWidth.value - offset - lineLength) - offset
@@ -151,35 +103,35 @@ trait MessageRendering {
151
103
}
152
104
153
105
msg.linesIterator
154
- .map { line => offsetBox + (if line.isEmpty then " " else padding + line) }
106
+ .map { line => " " * (offset - 1 ) + " | " + (if line.isEmpty then " " else padding + line) }
155
107
.mkString(EOL )
156
108
}
157
109
158
110
/** The source file path, line and column numbers from the given SourcePosition */
159
- protected def posFileStr (pos : SourcePosition ): String =
111
+ def posFileStr (pos : SourcePosition ): String =
160
112
val path = pos.source.file.path
161
113
if pos.exists then s " $path: ${pos.line + 1 }: ${pos.column}" else path
162
114
163
115
/** The separator between errors containing the source file and error type
164
116
*
165
117
* @return separator containing error location and kind
166
118
*/
167
- private def posStr (pos : SourcePosition , message : Message , diagnosticString : String )(using Context , Level , Offset ): String =
168
- if (pos.source != NoSourcePosition .source) hl({
169
- val realPos = pos.nonInlined
170
- val fileAndPos = posFileStr(realPos)
119
+ def posStr (pos : SourcePosition , diagnosticLevel : String , message : Message )(using Context ): String =
120
+ if (pos.source != NoSourcePosition .source) hl(diagnosticLevel)( {
121
+ val fileAndPos = posFileStr( pos.nonInlined)
122
+ val file = if fileAndPos.isEmpty || fileAndPos.endsWith( " " ) then fileAndPos else s " $fileAndPos "
171
123
val errId =
172
124
if (message.errorId ne ErrorMessageID .NoExplanationID ) {
173
125
val errorNumber = message.errorId.errorNumber
174
126
s " [E ${" 0" * (3 - errorNumber.toString.length) + errorNumber}] "
175
127
} else " "
176
128
val kind =
177
- if (message.kind == " " ) diagnosticString
178
- else s " ${message.kind} $diagnosticString "
179
- val title =
180
- if fileAndPos.isEmpty then s " $errId$kind : " // this happens in dotty.tools.repl.ScriptedTests // TODO add name of source or remove `:` (and update test files)
181
- else s " $errId$kind : $fileAndPos "
182
- boxTitle(title )
129
+ if (message.kind == " " ) diagnosticLevel
130
+ else s " ${message.kind} $diagnosticLevel "
131
+ val prefix = s " -- ${errId}${kind} : $file "
132
+
133
+ prefix +
134
+ ( " - " * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0 ) )
183
135
}) else " "
184
136
185
137
/** Explanation rendered under "Explanation" header */
@@ -194,7 +146,7 @@ trait MessageRendering {
194
146
sb.toString
195
147
}
196
148
197
- private def appendFilterHelp (dia : Diagnostic , sb : mutable.StringBuilder ): Unit =
149
+ def appendFilterHelp (dia : Diagnostic , sb : mutable.StringBuilder ): Unit =
198
150
import dia ._
199
151
val hasId = msg.errorId.errorNumber >= 0
200
152
val category = dia match {
@@ -214,34 +166,17 @@ trait MessageRendering {
214
166
/** The whole message rendered from `msg` */
215
167
def messageAndPos (dia : Diagnostic )(using Context ): String = {
216
168
import dia ._
217
- val pos1 = pos.nonInlined
218
- val inlineStack = inlinePosStack(pos).filter(_ != pos1)
219
- val maxLineNumber =
220
- if pos.exists then (pos1 :: inlineStack).map(_.endLine).max + 1
221
- else 0
222
- given Level = Level (level)
223
- given Offset = Offset (maxLineNumber.toString.length + 2 )
169
+ val levelString = diagnosticLevel(dia)
224
170
val sb = mutable.StringBuilder ()
225
- val posString = posStr(pos, msg, diagnosticLevel(dia) )
171
+ val posString = posStr(pos, levelString, msg )
226
172
if (posString.nonEmpty) sb.append(posString).append(EOL )
227
173
if (pos.exists) {
228
174
val pos1 = pos.nonInlined
229
175
if (pos1.exists && pos1.source.file.exists) {
230
- val (srcBefore, srcAfter, offset) = sourceLines(pos1)
231
- val marker = positionMarker(pos1)
232
- val err = errorMsg(pos1, msg.message)
233
- sb.append((srcBefore ::: marker :: err :: srcAfter).mkString(EOL ))
234
-
235
- if inlineStack.nonEmpty then
236
- sb.append(EOL ).append(newBox())
237
- sb.append(EOL ).append(offsetBox).append(i " Inline stack trace " )
238
- for inlinedPos <- inlineStack if inlinedPos != pos1 do
239
- val (srcBefore, srcAfter, offset) = sourceLines(inlinedPos)
240
- val marker = positionMarker(inlinedPos)
241
- sb.append(EOL ).append(newBox(soft = true ))
242
- sb.append(EOL ).append(offsetBox).append(i " This location contains code that was inlined from $pos" )
243
- sb.append(EOL ).append((srcBefore ::: marker :: srcAfter).mkString(EOL ))
244
- sb.append(EOL ).append(endBox)
176
+ val (srcBefore, srcAfter, offset) = sourceLines(pos1, levelString)
177
+ val marker = columnMarker(pos1, offset, levelString)
178
+ val err = errorMsg(pos1, msg.message, offset)
179
+ sb.append((srcBefore ::: marker :: err :: outer(pos, " " * (offset - 1 )) ::: srcAfter).mkString(EOL ))
245
180
}
246
181
else sb.append(msg.message)
247
182
}
@@ -251,13 +186,15 @@ trait MessageRendering {
251
186
sb.toString
252
187
}
253
188
254
- private def hl (str : String )(using Context , Level ): String =
255
- summon[Level ].value match
256
- case interfaces.Diagnostic .ERROR => Red (str).show
257
- case interfaces.Diagnostic .WARNING => Yellow (str).show
258
- case interfaces.Diagnostic .INFO => Blue (str).show
189
+ def hl (diagnosticLevel : String )(str : String )(using Context ): String = diagnosticLevel match {
190
+ case " Info" => Blue (str).show
191
+ case " Error" => Red (str).show
192
+ case _ =>
193
+ assert(diagnosticLevel.contains(" Warning" ))
194
+ Yellow (str).show
195
+ }
259
196
260
- private def diagnosticLevel (dia : Diagnostic ): String =
197
+ def diagnosticLevel (dia : Diagnostic ): String =
261
198
dia match {
262
199
case dia : FeatureWarning => " Feature Warning"
263
200
case dia : DeprecationWarning => " Deprecation Warning"
@@ -268,28 +205,4 @@ trait MessageRendering {
268
205
case interfaces.Diagnostic .WARNING => " Warning"
269
206
case interfaces.Diagnostic .INFO => " Info"
270
207
}
271
-
272
- }
273
-
274
- private object Highlight {
275
- opaque type Level = Int
276
- extension (level : Level ) def value : Int = level
277
- object Level :
278
- def apply (level : Int ): Level = level
279
- }
280
-
281
- /** Size of the left offset added by the box
282
- *
283
- * ```
284
- * -- Error: ... ------------
285
- * 4 | foo
286
- * | ^^^
287
- * ^^^ // size of this offset
288
- * ```
289
- */
290
- private object Offsets {
291
- opaque type Offset = Int
292
- def offset (using o : Offset ): Int = o
293
- object Offset :
294
- def apply (level : Int ): Offset = level
295
208
}
0 commit comments