Skip to content

Commit 6e4e7e2

Browse files
committed
Fix diagnostics covering trailing text
Diagnostics may sometimes cover trailing text which wasn't consumed. For example, `parseatom(")")` doesn't consume the errant trailing ')', but the diagnostic refers to this character. In this case, `SourceFile` needs to cover the location of the diagnostic in addition to any text which is consumed. As part of this, mark `sourcetext(::ParseStream)` as deprecated, in favour of constructing a `SourceFile` to wrap things up more neatly. Also fix a bug in `highlight()` for empty `SourceFile`s.
1 parent 8731bab commit 6e4e7e2

File tree

5 files changed

+35
-17
lines changed

5 files changed

+35
-17
lines changed

src/parse_stream.jl

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ function Base.show(io::IO, mime::MIME"text/plain", stream::ParseStream)
331331
end
332332

333333
function show_diagnostics(io::IO, stream::ParseStream)
334-
show_diagnostics(io, stream.diagnostics, sourcetext(stream))
334+
show_diagnostics(io, stream.diagnostics, SourceFile(stream))
335335
end
336336

337337
# We manage a pool of stream positions as parser working space
@@ -1078,17 +1078,8 @@ function build_tree(make_node::Function, ::Type{NodeType}, stream::ParseStream;
10781078
end
10791079
end
10801080

1081-
"""
1082-
sourcetext(stream::ParseStream; steal_textbuf=true)
1083-
1084-
Return the source text being parsed by this `ParseStream` as a UTF-8 encoded
1085-
string.
1086-
1087-
If `steal_textbuf==true`, this is permitted to steal the content of the
1088-
stream's text buffer. Note that this leaves the `ParseStream` in an invalid
1089-
state for further parsing.
1090-
"""
10911081
function sourcetext(stream::ParseStream; steal_textbuf=false)
1082+
Base.depwarn("Use of `sourcetext(::ParseStream)` is deprecated. Use `SourceFile(stream)` instead", :sourcetext)
10921083
root = stream.text_root
10931084
# The following kinda works but makes the return type of this method type
10941085
# unstable. (Also codeunit(root) == UInt8 doesn't imply UTF-8 encoding?)
@@ -1109,7 +1100,19 @@ function sourcetext(stream::ParseStream; steal_textbuf=false)
11091100
end
11101101

11111102
function SourceFile(stream::ParseStream; kws...)
1112-
return SourceFile(sourcetext(stream); first_index=first_byte(stream), kws...)
1103+
fbyte = first_byte(stream)
1104+
lbyte = maximum((last_byte(d) for d in stream.diagnostics);
1105+
init=last_byte(stream))
1106+
# See also sourcetext()
1107+
srcroot = stream.text_root
1108+
str = if srcroot isa String
1109+
SubString(srcroot, fbyte, thisind(srcroot, lbyte))
1110+
elseif srcroot isa SubString{String}
1111+
SubString(srcroot, fbyte, thisind(srcroot, lbyte))
1112+
else
1113+
SubString(String(stream.textbuf[fbyte:lbyte]))
1114+
end
1115+
return SourceFile(str; first_index=first_byte(stream), kws...)
11131116
end
11141117

11151118
"""

src/parser.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3561,7 +3561,8 @@ function parse_atom(ps::ParseState, check_identifiers=true)
35613561
msg = leading_kind == K"EndMarker" ?
35623562
"premature end of input" :
35633563
"unexpected `$(untokenize(leading_kind))`"
3564-
bump_invisible(ps, K"error", error=msg)
3564+
emit_diagnostic(ps, error=msg)
3565+
bump_invisible(ps, K"error")
35653566
else
35663567
bump(ps, error="invalid syntax atom")
35673568
end

src/source_files.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@ function highlight(io::IO, source::SourceFile, range::UnitRange;
219219
print(io, source[x:p-1])
220220
_printstyled(io, hitext; bgcolor=color)
221221
print(io, source[q+1:d])
222-
source[thisind(source, d)] == '\n' || print(io, "\n")
222+
if d >= firstindex(source) && source[thisind(source, d)] != '\n'
223+
print(io, "\n")
224+
end
223225
_print_marker_line(io, source[a:p-1], hitext, true, true, marker_line_color, note, notecolor)
224226
else
225227
# x --------------

test/diagnostics.jl

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
function diagnostic(str; only_first=false, allow_multiple=false)
1+
function diagnostic(str; only_first=false, allow_multiple=false, rule=:all)
22
stream = ParseStream(str)
3-
parse!(stream)
3+
parse!(stream, rule=rule)
44
if allow_multiple
55
stream.diagnostics
66
else
@@ -52,7 +52,11 @@ end
5252
Diagnostic(6, 5, :error, "invalid macro name")
5353

5454
@test diagnostic("a, , b") ==
55-
Diagnostic(4, 3, :error, "unexpected `,`")
55+
Diagnostic(4, 4, :error, "unexpected `,`")
56+
@test diagnostic(")", allow_multiple=true) == [
57+
Diagnostic(1, 1, :error, "unexpected `)`")
58+
Diagnostic(1, 1, :error, "extra tokens after end of expression")
59+
]
5660

5761
@test diagnostic("if\nfalse\nend") ==
5862
Diagnostic(3, 3, :error, "missing condition in `if`")
@@ -99,6 +103,11 @@ end
99103

100104
@test diagnostic("\"\$(x,y)\"") ==
101105
Diagnostic(3, 7, :error, "invalid interpolation syntax")
106+
107+
@test diagnostic("", rule=:statement) ==
108+
Diagnostic(1, 0, :error, "premature end of input")
109+
@test diagnostic("", rule=:atom) ==
110+
Diagnostic(1, 0, :error, "premature end of input")
102111
end
103112

104113
@testset "parser warnings" begin

test/source_files.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ end
115115
@test sprint(highlight, SourceFile("a\nα"), 1:4) == "\na\nα\n"
116116
@test sprint(highlight, SourceFile("a\nb\nα"), 3:3) == "a\nb\n\nα"
117117

118+
# empty files
119+
@test sprint(highlight, SourceFile(""), 1:0) == ""
120+
118121
# Multi-line ranges
119122
@test sprint(highlight, src, 1:7) == """
120123
┌───

0 commit comments

Comments
 (0)