@@ -16,8 +16,6 @@ import scala.annotation.switch
1616import scala .collection .mutable
1717
1818trait MessageRendering {
19- import Highlight .*
20- import Offsets .*
2119
2220 /** Remove ANSI coloring from `str`, useful for getting real length of
2321 * strings
@@ -27,25 +25,31 @@ trait MessageRendering {
2725 def stripColor (str : String ): String =
2826 str.replaceAll(" \u001b\\ [.*?m" , " " )
2927
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)
3337 else Nil
3438
3539 /** Get the sourcelines before and after the position, as well as the offset
3640 * for rendering line numbers
3741 *
3842 * @return (lines before error, lines after error, line numbers offset)
3943 */
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 ) = {
4145 assert(pos.exists && pos.source.file.exists)
4246 var maxLen = Int .MinValue
4347 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 } | "
4751 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)
4953 lnum + line.stripLineEnd
5054 }
5155
@@ -73,75 +77,23 @@ trait MessageRendering {
7377 )
7478 }
7579
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 )
9483 val padding = pos.startColumnPadding
95- val carets =
84+ val carets = hl(diagnosticLevel) {
9685 if (pos.startLine == pos.endLine)
9786 " ^" * math.max(1 , pos.endColumn - pos.startColumn)
9887 else " ^"
99- hl(s " $offsetBox$padding$carets" )
88+ }
89+ s " $prefix| $padding$carets"
10090 }
10191
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-
14092 /** The error message (`msg`) aligned under `pos`
14193 *
14294 * @return aligned error message
14395 */
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 = {
14597 val padding = msg.linesIterator.foldLeft(pos.startColumnPadding) { (pad, line) =>
14698 val lineLength = stripColor(line).length
14799 val maxPad = math.max(0 , ctx.settings.pageWidth.value - offset - lineLength) - offset
@@ -151,35 +103,35 @@ trait MessageRendering {
151103 }
152104
153105 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) }
155107 .mkString(EOL )
156108 }
157109
158110 /** 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 =
160112 val path = pos.source.file.path
161113 if pos.exists then s " $path: ${pos.line + 1 }: ${pos.column}" else path
162114
163115 /** The separator between errors containing the source file and error type
164116 *
165117 * @return separator containing error location and kind
166118 */
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 "
171123 val errId =
172124 if (message.errorId ne ErrorMessageID .NoExplanationID ) {
173125 val errorNumber = message.errorId.errorNumber
174126 s " [E ${" 0" * (3 - errorNumber.toString.length) + errorNumber}] "
175127 } else " "
176128 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 ) )
183135 }) else " "
184136
185137 /** Explanation rendered under "Explanation" header */
@@ -194,7 +146,7 @@ trait MessageRendering {
194146 sb.toString
195147 }
196148
197- private def appendFilterHelp (dia : Diagnostic , sb : mutable.StringBuilder ): Unit =
149+ def appendFilterHelp (dia : Diagnostic , sb : mutable.StringBuilder ): Unit =
198150 import dia ._
199151 val hasId = msg.errorId.errorNumber >= 0
200152 val category = dia match {
@@ -214,34 +166,17 @@ trait MessageRendering {
214166 /** The whole message rendered from `msg` */
215167 def messageAndPos (dia : Diagnostic )(using Context ): String = {
216168 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)
224170 val sb = mutable.StringBuilder ()
225- val posString = posStr(pos, msg, diagnosticLevel(dia) )
171+ val posString = posStr(pos, levelString, msg )
226172 if (posString.nonEmpty) sb.append(posString).append(EOL )
227173 if (pos.exists) {
228174 val pos1 = pos.nonInlined
229175 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 ))
245180 }
246181 else sb.append(msg.message)
247182 }
@@ -251,13 +186,15 @@ trait MessageRendering {
251186 sb.toString
252187 }
253188
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+ }
259196
260- private def diagnosticLevel (dia : Diagnostic ): String =
197+ def diagnosticLevel (dia : Diagnostic ): String =
261198 dia match {
262199 case dia : FeatureWarning => " Feature Warning"
263200 case dia : DeprecationWarning => " Deprecation Warning"
@@ -268,28 +205,4 @@ trait MessageRendering {
268205 case interfaces.Diagnostic .WARNING => " Warning"
269206 case interfaces.Diagnostic .INFO => " Info"
270207 }
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
295208}
0 commit comments