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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
# frozen_string_literal: true
module Parser
##
# @api public
#
# @!attribute [r] level
# @see LEVELS
# @return [Symbol] diagnostic level
#
# @!attribute [r] reason
# @see Parser::MESSAGES
# @return [Symbol] reason for error
#
# @!attribute [r] arguments
# @see Parser::MESSAGES
# @return [Symbol] extended arguments that describe the error
#
# @!attribute [r] message
# @return [String] error message
#
# @!attribute [r] location
# Main error-related source range.
# @return [Parser::Source::Range]
#
# @!attribute [r] highlights
# Supplementary error-related source ranges.
# @return [Array<Parser::Source::Range>]
#
class Diagnostic
##
# Collection of the available diagnostic levels.
#
# @return [Array]
#
LEVELS = [:note, :warning, :error, :fatal].freeze
attr_reader :level, :reason, :arguments
attr_reader :location, :highlights
##
# @param [Symbol] level
# @param [Symbol] reason
# @param [Hash] arguments
# @param [Parser::Source::Range] location
# @param [Array<Parser::Source::Range>] highlights
#
def initialize(level, reason, arguments, location, highlights=[])
unless LEVELS.include?(level)
raise ArgumentError,
"Diagnostic#level must be one of #{LEVELS.join(', ')}; " \
"#{level.inspect} provided."
end
raise 'Expected a location' unless location
@level = level
@reason = reason
@arguments = (arguments || {}).dup.freeze
@location = location
@highlights = highlights.dup.freeze
freeze
end
##
# @return [String] the rendered message.
#
def message
Messages.compile(@reason, @arguments)
end
##
# Renders the diagnostic message as a clang-like diagnostic.
#
# @example
# diagnostic.render # =>
# # [
# # "(fragment:0):1:5: error: unexpected token $end",
# # "foo +",
# # " ^"
# # ]
#
# @return [Array<String>]
#
def render
if @location.line == @location.last_line || @location.is?("\n")
["#{@location}: #{@level}: #{message}"] + render_line(@location)
else
# multi-line diagnostic
first_line = first_line_only(@location)
last_line = last_line_only(@location)
num_lines = (@location.last_line - @location.line) + 1
buffer = @location.source_buffer
last_lineno, last_column = buffer.decompose_position(@location.end_pos)
["#{@location}-#{last_lineno}:#{last_column}: #{@level}: #{message}"] +
render_line(first_line, num_lines > 2, false) +
render_line(last_line, false, true)
end
end
private
##
# Renders one source line in clang diagnostic style, with highlights.
#
# @return [Array<String>]
#
def render_line(range, ellipsis=false, range_end=false)
source_line = range.source_line
highlight_line = ' ' * source_line.length
@highlights.each do |highlight|
line_range = range.source_buffer.line_range(range.line)
if highlight = highlight.intersect(line_range)
highlight_line[highlight.column_range] = '~' * highlight.size
end
end
if range.is?("\n")
highlight_line += "^"
else
if !range_end && range.size >= 1
highlight_line[range.column_range] = '^' + '~' * (range.size - 1)
else
highlight_line[range.column_range] = '~' * range.size
end
end
highlight_line += '...' if ellipsis
[source_line, highlight_line].
map { |line| "#{range.source_buffer.name}:#{range.line}: #{line}" }
end
##
# If necessary, shrink a `Range` so as to include only the first line.
#
# @return [Parser::Source::Range]
#
def first_line_only(range)
if range.line != range.last_line
range.resize(range.source =~ /\n/)
else
range
end
end
##
# If necessary, shrink a `Range` so as to include only the last line.
#
# @return [Parser::Source::Range]
#
def last_line_only(range)
if range.line != range.last_line
range.adjust(begin_pos: range.source =~ /[^\n]*\z/)
else
range
end
end
end
end
|