Module: Kumi::Frontends::SourceFrame
- Defined in:
- lib/kumi/frontends/source_frame.rb
Overview
Renders a parse/semantic error against its source as a ‘file:line:col` header plus a caret-annotated code frame. The location is taken from the error’s structured ‘Location` when present; we no longer scrape it out of the message text, which is what produced the flaky `?:?` headers.
Class Method Summary collapse
-
.clean_message(message) ⇒ Object
Strip the canonical ‘file:line:col:` / `file:line:` location prefix the message may already carry (LocatedError#to_s and ErrorEntry#to_s both prepend it), so render adds the header exactly once.
- .code_frame(src, line, col, context: 2) ⇒ Object
-
.frame_row(text, number, target_line, col) ⇒ Object
The source line plus, when it is the target line and we know the column, a caret pointer beneath it.
-
.location_of(error) ⇒ Object
The structured location, if the error exposes a line and column.
-
.render(error, src:, file_label:) ⇒ Object
Build the full user-facing error string for ‘error` raised while loading `src` labelled `file_label`.
Class Method Details
.clean_message(message) ⇒ Object
Strip the canonical ‘file:line:col:` / `file:line:` location prefix the message may already carry (LocatedError#to_s and ErrorEntry#to_s both prepend it), so render adds the header exactly once. Now that every location renders one way, this is a single prefix to remove.
45 46 47 |
# File 'lib/kumi/frontends/source_frame.rb', line 45 def () .to_s.sub(/\A\S+:\d+(?::\d+)?:\s+/, "").strip end |
.code_frame(src, line, col, context: 2) ⇒ Object
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/kumi/frontends/source_frame.rb', line 49 def code_frame(src, line, col, context: 2) return "" if line.nil? lines = src.lines return "" if lines.empty? from = [line - 1 - context, 0].max to = [line - 1 + context, lines.length - 1].min (from..to).flat_map { |i| frame_row(lines[i], i + 1, line, col) }.join("\n") end |
.frame_row(text, number, target_line, col) ⇒ Object
The source line plus, when it is the target line and we know the column, a caret pointer beneath it. Column 0/nil means “column unknown” — skip it.
62 63 64 65 66 67 68 |
# File 'lib/kumi/frontends/source_frame.rb', line 62 def frame_row(text, number, target_line, col) marker = number == target_line ? "➤" : " " row = format("%s %4d | %s", marker, number, text.to_s.rstrip) return [row] unless number == target_line && col && col >= 1 [row, format(" | %s^", " " * (col - 1))] end |
.location_of(error) ⇒ Object
The structured location, if the error exposes a line and column. Both Kumi’s LocatedError and the parser’s SyntaxError satisfy this; anything else falls through to nil rather than a guessed coordinate.
31 32 33 34 35 36 37 38 39 |
# File 'lib/kumi/frontends/source_frame.rb', line 31 def location_of(error) return nil unless error.respond_to?(:location) loc = error.location return nil unless loc.respond_to?(:line) return nil if loc.line.nil? loc end |
.render(error, src:, file_label:) ⇒ Object
Build the full user-facing error string for ‘error` raised while loading `src` labelled `file_label`. When the error carries a usable location we emit `file:line:col: message` and a code frame; when it does not, we emit a clean `file: message` with no invented coordinates.
16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/kumi/frontends/source_frame.rb', line 16 def render(error, src:, file_label:) loc = location_of(error) = (error.) return "#{file_label}: #{}" unless loc # Render the header through Location#to_s (the one location dialect), # substituting the caller's file label for the path. header = "#{Kumi::Syntax::Location.new(file: file_label, line: loc.line, column: loc.column)}: #{}" frame = code_frame(src, loc.line, loc.column) frame.empty? ? header : "#{header}\n#{frame}" end |