-
-
Notifications
You must be signed in to change notification settings - Fork 35
/
diagnostics.jl
131 lines (114 loc) · 4.73 KB
/
diagnostics.jl
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
"""
Diagnostic(first_byte, last_byte; [error="msg" | warning="msg"])
A diagnostic message, referring to the source code byte range
first_byte:last_byte, with a `warning` or `error` message.
Messages should be concise, matter-of-fact and not include decorations:
* Concise: "Show don't tell". Where possible, let's show the user what's wrong
by annotating their original source code via the byte range.
* Matter-of-fact: Admonishing the user isn't helpful. Let's gently show them
what's wrong instead, using a neutral tone.
* Decorations: Capitalization, punctuation and diagnostic class ("error" /
"warning") should be omitted. These decorations will be added by the
formatting code.
TODO: At some point we should enhance Diagnostic to allow multiple sub-ranges
for better annotation. Let's follow the excellent precedent set by Rust's
[rustc_errors::Diagnostic](https://doc.rust-lang.org/stable/nightly-rustc/rustc_errors/struct.Diagnostic.html).
TODO: We should cater for extended descriptions containing multiple sentences
via a diagnostic code which can be used to look up detailed information. Again,
Rust does this well.
"""
struct Diagnostic
first_byte::Int
last_byte::Int
level::Symbol
message::String
end
function Diagnostic(first_byte, last_byte; error=nothing, warning=nothing)
message = !isnothing(error) ? error :
!isnothing(warning) ? warning :
Base.error("No message in diagnostic")
level = !isnothing(error) ? :error : :warning
Diagnostic(first_byte, last_byte, level, message)
end
first_byte(d::Diagnostic) = d.first_byte
last_byte(d::Diagnostic) = d.last_byte
is_error(d::Diagnostic) = d.level == :error
function show_diagnostic(io::IO, diagnostic::Diagnostic, source::SourceFile)
color,prefix = diagnostic.level == :error ? (:light_red, "Error") :
diagnostic.level == :warning ? (:light_yellow, "Warning") :
diagnostic.level == :note ? (:light_blue, "Note") :
(:normal, "Info")
line, col = source_location(source, first_byte(diagnostic))
linecol = "$line:$col"
filename = source.filename
if !isnothing(filename)
locstr = "$filename:$linecol"
if get(io, :color, false)
# Also add hyperlinks in color terminals
url = "file://$(abspath(filename))#$linecol"
locstr = "\e]8;;$url\e\\$locstr\e]8;;\e\\"
end
else
locstr = "line $linecol"
end
print(io, prefix, ": ")
printstyled(io, diagnostic.message, color=color)
printstyled(io, "\n", "@ $locstr", color=:light_black)
print(io, "\n")
p = first_byte(diagnostic)
q = last_byte(diagnostic)
text = sourcetext(source)
if q < p || (p == q && source[p] == '\n')
# An empty or invisible range! We expand it symmetrically to make it
# visible.
p = max(firstindex(text), prevind(text, p))
q = min(lastindex(text), nextind(text, q))
end
# p and q mark the start and end of the diagnostic range. For context,
# buffer these out to the surrouding lines.
a,b = source_line_range(source, p, context_lines_before=2, context_lines_after=1)
c,d = source_line_range(source, q, context_lines_before=1, context_lines_after=2)
hicol = (100,40,40)
# TODO: show line numbers on left
print(io, source[a:prevind(text, p)])
# There's two situations, either
if b >= c
# The diagnostic range is compact and we show the whole thing
# a...............
# .....p...q......
# ...............b
_printstyled(io, source[p:q]; bgcolor=hicol)
else
# Or large and we trucate the code to show only the region around the
# start and end of the error.
# a...............
# .....p..........
# ...............b
# (snip)
# c...............
# .....q..........
# ...............d
_printstyled(io, source[p:b]; bgcolor=hicol)
println(io, "…")
_printstyled(io, source[c:q]; bgcolor=hicol)
end
print(io, source[nextind(text,q):d])
println(io)
end
function show_diagnostics(io::IO, diagnostics::AbstractVector{Diagnostic}, source::SourceFile)
for d in diagnostics
show_diagnostic(io, d, source)
end
end
function show_diagnostics(io::IO, diagnostics::AbstractVector{Diagnostic}, text::AbstractString)
if !isempty(diagnostics)
show_diagnostics(io, diagnostics, SourceFile(text))
end
end
function emit_diagnostic(diagnostics::AbstractVector{Diagnostic},
fbyte::Integer, lbyte::Integer; kws...)
push!(diagnostics, Diagnostic(fbyte, lbyte; kws...))
end
function any_error(diagnostics::AbstractVector{Diagnostic})
any(is_error(d) for d in diagnostics)
end