Class: LexerKit::Core::Diagnostic

Inherits:
Object
  • Object
show all
Defined in:
lib/lexer_kit/core/diagnostic.rb

Overview

Diagnostic holds error information with position and notes. It can render a human-readable error message with source context.

Constant Summary collapse

LEVEL_LABELS =
{
  error: "error",
  warning: "warning",
  note: "note"
}.freeze
LEVEL_COLORS =
{
  error: "\e[1;31m",    # bold red
  warning: "\e[1;33m",  # bold yellow
  note: "\e[1;36m"      # bold cyan
}.freeze
RESET =
"\e[0m"
BOLD =
"\e[1m"
BLUE =
"\e[34m"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(level:, message:, span:, notes: nil) ⇒ Diagnostic

Returns a new instance of Diagnostic.

Parameters:

  • level (Symbol)

    :error, :warning, or :note

  • message (String)

    main error message

  • span (Span)

    location in source

  • notes (Array<String>, nil) (defaults to: nil)

    additional notes



30
31
32
33
34
35
# File 'lib/lexer_kit/core/diagnostic.rb', line 30

def initialize(level:, message:, span:, notes: nil)
  @level = level
  @message = message
  @span = span
  @notes = notes&.freeze
end

Instance Attribute Details

#levelObject (readonly)

Returns the value of attribute level.



8
9
10
# File 'lib/lexer_kit/core/diagnostic.rb', line 8

def level
  @level
end

#messageObject (readonly)

Returns the value of attribute message.



8
9
10
# File 'lib/lexer_kit/core/diagnostic.rb', line 8

def message
  @message
end

#notesObject (readonly)

Returns the value of attribute notes.



8
9
10
# File 'lib/lexer_kit/core/diagnostic.rb', line 8

def notes
  @notes
end

#spanObject (readonly)

Returns the value of attribute span.



8
9
10
# File 'lib/lexer_kit/core/diagnostic.rb', line 8

def span
  @span
end

Instance Method Details

#inspectObject



98
99
100
# File 'lib/lexer_kit/core/diagnostic.rb', line 98

def inspect
  "#<LexerKit::Core::Diagnostic #{@level} #{@span}: #{@message.inspect}>"
end

#render(source, color: $stdout.tty?) ⇒ String

Render the diagnostic with source context

Parameters:

  • source (Source)
  • color (Boolean) (defaults to: $stdout.tty?)

    enable ANSI colors

Returns:

  • (String)


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
# File 'lib/lexer_kit/core/diagnostic.rb', line 41

def render(source, color: $stdout.tty?)
  lines = []
  line_num, col = source.line_col(@span.start)

  # Header: filename:line:col: level: message
  loc = source.filename ? "#{source.filename}:" : ""
  loc += "#{line_num}:#{col}"

  if color
    level_str = "#{LEVEL_COLORS[@level]}#{LEVEL_LABELS[@level]}#{RESET}"
    lines << "#{BOLD}#{loc}:#{RESET} #{level_str}: #{BOLD}#{@message}#{RESET}"
  else
    lines << "#{loc}: #{LEVEL_LABELS[@level]}: #{@message}"
  end

  # Source line
  line_content = source.line_slice(line_num)
  if line_content
    line_num_str = line_num.to_s
    gutter_width = line_num_str.length

    # Line number gutter
    lines << if color
               " #{BLUE}#{line_num_str} |#{RESET} #{line_content}"
             else
               " #{line_num_str} | #{line_content}"
             end

    # Caret line
    highlight_len = [@span.len, line_content.length - col + 1].min
    highlight_len = 1 if highlight_len < 1
    carets = "^" + ("~" * (highlight_len - 1)) # rubocop:disable Style/StringConcatenation
    padding = " " * (col - 1)

    lines << if color
               " #{' ' * gutter_width} #{BLUE}|#{RESET} #{padding}#{LEVEL_COLORS[@level]}#{carets}#{RESET}"
             else
               " #{' ' * gutter_width} | #{padding}#{carets}"
             end
  end

  # Notes
  @notes&.each do |note|
    lines << if color
               "       #{BOLD}note:#{RESET} #{note}"
             else
               "       note: #{note}"
             end
  end

  lines.join("\n")
end

#to_sObject



94
95
96
# File 'lib/lexer_kit/core/diagnostic.rb', line 94

def to_s
  "#{LEVEL_LABELS[@level]}: #{@message} at #{@span}"
end