Skip to content

Commit 2922893

Browse files
authored
Adds support for default error reporting (#83)
- By raising SyntaxTree::Parser::ParseError with default arguments it returns much nicer error messages.
1 parent dbcdf3f commit 2922893

File tree

3 files changed

+143
-55
lines changed

3 files changed

+143
-55
lines changed

lib/syntax_tree/erb/parser.rb

Lines changed: 111 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ module ERB
55
class Parser
66
# This is the parent class of any kind of errors that will be raised by
77
# the parser.
8-
class ParseError < StandardError
9-
end
108

119
# This error occurs when a certain token is expected in a certain place
1210
# but is not found. Sometimes this is handled internally because some
1311
# elements are optional. Other times it is not and it is raised to end the
1412
# parsing process.
15-
class MissingTokenError < ParseError
13+
class MissingTokenError < SyntaxTree::Parser::ParseError
1614
end
1715

1816
attr_reader :source, :tokens
@@ -52,7 +50,13 @@ def parse_any_tag
5250

5351
if tag.is_a?(Doctype)
5452
if @found_doctype
55-
raise(ParseError, "Only one doctype element is allowed")
53+
raise(
54+
SyntaxTree::Parser::ParseError.new(
55+
"Only one doctype element is allowed",
56+
tag.location.start_line,
57+
0
58+
)
59+
)
5660
else
5761
@found_doctype = true
5862
end
@@ -123,8 +127,13 @@ def make_tokens
123127
# abc
124128
enum.yield :text, $&, index, line
125129
else
126-
raise ParseError,
127-
"Unexpected character at #{index}: #{source[index]}"
130+
raise(
131+
SyntaxTree::Parser::ParseError.new(
132+
"Unexpected character at #{index}: #{source[index]}",
133+
line,
134+
0
135+
)
136+
)
128137
end
129138
in :erb_start
130139
case source[index..]
@@ -207,8 +216,13 @@ def make_tokens
207216
# abc
208217
enum.yield :text, $&, index, line
209218
else
210-
raise ParseError,
211-
"Unexpected character in string at #{index}: #{source[index]}"
219+
raise(
220+
SyntaxTree::Parser::ParseError.new(
221+
"Unexpected character in string at #{index}: #{source[index]}",
222+
line,
223+
0
224+
)
225+
)
212226
end
213227
in :string_double_quote
214228
case source[index..]
@@ -230,8 +244,13 @@ def make_tokens
230244
# abc
231245
enum.yield :text, $&, index, line
232246
else
233-
raise ParseError,
234-
"Unexpected character in string at #{index}: #{source[index]}"
247+
raise(
248+
SyntaxTree::Parser::ParseError.new(
249+
"Unexpected character in string at #{index}: #{source[index]}",
250+
line,
251+
0
252+
)
253+
)
235254
end
236255
in :inside
237256
case source[index..]
@@ -284,8 +303,13 @@ def make_tokens
284303
enum.yield :string_open_single_quote, $&, index, line
285304
state << :string_single_quote
286305
else
287-
raise ParseError,
288-
"Unexpected character at #{index}: #{source[index]}"
306+
raise(
307+
SyntaxTree::Parser::ParseError.new(
308+
"Unexpected character at #{index}: #{source[index]}",
309+
line,
310+
0
311+
)
312+
)
289313
end
290314
end
291315

@@ -304,7 +328,13 @@ def consume(expected)
304328
type, value, index, line = tokens.peek
305329

306330
if expected != type
307-
raise MissingTokenError, "expected #{expected} got #{type}"
331+
raise(
332+
MissingTokenError.new(
333+
"expected #{expected} got #{type}",
334+
line,
335+
index
336+
)
337+
)
308338
end
309339

310340
tokens.next
@@ -335,7 +365,9 @@ def maybe
335365
# Otherwise we'll return the value returned by the block.
336366
def atleast
337367
result = yield
338-
raise MissingTokenError if result.nil?
368+
if result.nil?
369+
raise(MissingTokenError.new("No matching token", nil, nil))
370+
end
339371
result
340372
end
341373

@@ -372,7 +404,13 @@ def parse_html_opening_tag
372404
name = consume(:name)
373405

374406
if name.value =~ /\A[@:#]/
375-
raise ParseError, "Invalid html-tag name #{name}"
407+
raise(
408+
SyntaxTree::Parser::ParseError.new(
409+
"Invalid html-tag name #{name}",
410+
name.location.start_line,
411+
0
412+
)
413+
)
376414
end
377415

378416
attributes =
@@ -431,15 +469,21 @@ def parse_html_element
431469

432470
if closing.nil?
433471
raise(
434-
ParseError,
435-
"Missing closing tag for <#{opening.name.value}> at #{opening.location}"
472+
SyntaxTree::Parser::ParseError.new(
473+
"Missing closing tag for <#{opening.name.value}> at #{opening.location}",
474+
opening.location.start_line,
475+
0
476+
)
436477
)
437478
end
438479

439480
if closing.name.value != opening.name.value
440481
raise(
441-
ParseError,
442-
"Expected closing tag for <#{opening.name.value}> but got <#{closing.name.value}> at #{closing.location}"
482+
SyntaxTree::Parser::ParseError.new(
483+
"Expected closing tag for <#{opening.name.value}> but got <#{closing.name.value}> at #{closing.location}",
484+
closing.location.start_line,
485+
0
486+
)
443487
)
444488
end
445489

@@ -462,8 +506,11 @@ def parse_erb_case(erb_node)
462506
unless erb_tag.is_a?(ErbCaseWhen) || erb_tag.is_a?(ErbElse) ||
463507
erb_tag.is_a?(ErbEnd)
464508
raise(
465-
ParseError,
466-
"Found no matching erb-tag to the if-tag at #{erb_node.location}"
509+
SyntaxTree::Parser::ParseError.new(
510+
"Found no matching erb-tag to the if-tag at #{erb_node.location}",
511+
erb_node.location.start_line,
512+
0
513+
)
467514
)
468515
end
469516

@@ -484,8 +531,11 @@ def parse_erb_case(erb_node)
484531
)
485532
else
486533
raise(
487-
ParseError,
488-
"Found no matching when- or else-tag to the case-tag at #{erb_node.location}"
534+
SyntaxTree::Parser::ParseError.new(
535+
"Found no matching when- or else-tag to the case-tag at #{erb_node.location}",
536+
erb_node.location.start_line,
537+
0
538+
)
489539
)
490540
end
491541
end
@@ -501,8 +551,11 @@ def parse_erb_if(erb_node)
501551

502552
unless erb_tag.is_a?(ErbControl) || erb_tag.is_a?(ErbEnd)
503553
raise(
504-
ParseError,
505-
"Found no matching erb-tag to the if-tag at #{erb_node.location}"
554+
SyntaxTree::Parser::ParseError.new(
555+
"Found no matching erb-tag to the if-tag at #{erb_node.location}",
556+
erb_node.location.start_line,
557+
0
558+
)
506559
)
507560
end
508561

@@ -530,8 +583,11 @@ def parse_erb_if(erb_node)
530583
)
531584
else
532585
raise(
533-
ParseError,
534-
"Found no matching elsif- or else-tag to the if-tag at #{erb_node.location}"
586+
SyntaxTree::Parser::ParseError.new(
587+
"Found no matching elsif- or else-tag to the if-tag at #{erb_node.location}",
588+
erb_node.location.start_line,
589+
0
590+
)
535591
)
536592
end
537593
end
@@ -543,8 +599,11 @@ def parse_erb_else(erb_node)
543599

544600
unless erb_end.is_a?(ErbEnd)
545601
raise(
546-
ParseError,
547-
"Found no matching end-tag for the else-tag at #{erb_node.location}"
602+
SyntaxTree::Parser::ParseError.new(
603+
"Found no matching end-tag for the else-tag at #{erb_node.location}",
604+
erb_node.location.start_line,
605+
0
606+
)
548607
)
549608
end
550609

@@ -582,8 +641,11 @@ def parse_erb_tag
582641

583642
if !closing_tag.is_a?(ErbClose)
584643
raise(
585-
ParseError,
586-
"Found no matching closing tag for the erb-tag at #{opening_tag.location}"
644+
SyntaxTree::Parser::ParseError.new(
645+
"Found no matching closing tag for the erb-tag at #{opening_tag.location}",
646+
opening_tag.location.start_line,
647+
0
648+
)
587649
)
588650
end
589651

@@ -615,8 +677,11 @@ def parse_erb_tag
615677

616678
unless erb_end.is_a?(ErbEnd)
617679
raise(
618-
ParseError,
619-
"Found no matching end-tag for the do-tag at #{erb_node.location}"
680+
SyntaxTree::Parser::ParseError.new(
681+
"Found no matching end-tag for the do-tag at #{erb_node.location}",
682+
erb_node.location.start_line,
683+
0
684+
)
620685
)
621686
end
622687

@@ -630,13 +695,23 @@ def parse_erb_tag
630695
erb_node
631696
end
632697
end
633-
rescue MissingTokenError => error
698+
rescue SyntaxTree::Parser::ParseError => error
634699
# If we have parsed tokens that we cannot process after we parsed <%, we should throw a ParseError
635700
# and not let it be handled by a `maybe`.
636701
if opening_tag
702+
message =
703+
if error.message.include?("Could not parse ERB-tag")
704+
error.message
705+
else
706+
"Could not parse ERB-tag: #{error.message}"
707+
end
708+
637709
raise(
638-
ParseError,
639-
"Could not parse ERB-tag at #{opening_tag.location}"
710+
SyntaxTree::Parser::ParseError.new(
711+
message,
712+
opening_tag.location.start_line,
713+
0
714+
)
640715
)
641716
else
642717
raise(error)

test/erb_test.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ def test_empty_file
1212
end
1313

1414
def test_missing_erb_end_tag
15-
assert_raises(SyntaxTree::ERB::Parser::ParseError) do
15+
assert_raises(SyntaxTree::Parser::ParseError) do
1616
ERB.parse("<% if no_end_tag %>")
1717
end
1818
end
1919

2020
def test_missing_erb_block_end_tag
21-
assert_raises(SyntaxTree::ERB::Parser::ParseError) do
21+
assert_raises(SyntaxTree::Parser::ParseError) do
2222
ERB.parse("<% no_end_tag do %>")
2323
end
2424
end
2525

2626
def test_missing_erb_case_end_tag
27-
assert_raises(SyntaxTree::ERB::Parser::ParseError) do
27+
assert_raises(SyntaxTree::Parser::ParseError) do
2828
ERB.parse("<% case variabel %>\n<% when 1>\n Hello\n")
2929
end
3030
end
@@ -35,6 +35,23 @@ def test_erb_code_with_non_ascii
3535
assert_instance_of(SyntaxTree::ERB::ErbNode, parsed.elements.first)
3636
end
3737

38+
def test_erb_errors
39+
example = <<-HTML
40+
<ul>
41+
<% if @items.each do |i|%>
42+
<li><%= i %></li>
43+
<% end.blank? %>
44+
<li>No items</li>
45+
<% end %>
46+
</ul>
47+
HTML
48+
ERB.parse(example)
49+
rescue SyntaxTree::Parser::ParseError => error
50+
assert_equal(2, error.lineno)
51+
assert_equal(0, error.column)
52+
assert_match(/Could not parse ERB-tag/, error.message)
53+
end
54+
3855
def test_if_and_end_in_same_output_tag_short
3956
source = "<%= if true\n what\nend %>"
4057
expected = "<%= what if true %>\n"

0 commit comments

Comments
 (0)