Skip to content

Commit 15e70b8

Browse files
committed
Try testing with RecursiveDescentParser as default; improve handling for keywords used as names; improve error messages
1 parent 4f6e18b commit 15e70b8

File tree

3 files changed

+144
-36
lines changed

3 files changed

+144
-36
lines changed

lib/graphql.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class RequiredImplementationMissingError < Error
3333

3434
class << self
3535
def default_parser
36-
@default_parser ||= GraphQL::Language::Parser
36+
@default_parser ||= GraphQL::Language::RecursiveDescentParser
3737
end
3838

3939
attr_writer :default_parser

lib/graphql/language/recursive_descent_parser.rb

Lines changed: 130 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,26 @@ class RecursiveDescentParser
99
include GraphQL::Language::Nodes
1010
include EmptyObjects
1111

12-
def self.parse(graphql_str)
13-
self.new(graphql_str).parse
12+
def self.parse(graphql_str, filename: nil, trace: Tracing::NullTrace)
13+
self.new(graphql_str, filename: filename, trace: trace).parse
1414
end
1515

16-
def initialize(graphql_str)
16+
def initialize(graphql_str, filename: nil, trace: Tracing::NullTrace)
17+
if graphql_str.nil?
18+
raise GraphQL::ParseError.new("No query string was present", nil, nil, nil)
19+
end
1720
@lexer = Lexer.new(graphql_str)
1821
@graphql_str = graphql_str
22+
@filename = filename
23+
@trace = trace
1924
end
2025

2126
def parse
22-
@document ||= document
27+
@document ||= begin
28+
@trace.parse(query_string: @graphql_str) do
29+
document
30+
end
31+
end
2332
end
2433

2534
private
@@ -48,8 +57,9 @@ def definition
4857
when :FRAGMENT
4958
loc = pos
5059
expect_token :FRAGMENT
51-
expect_token(:IDENTIFIER) if at?(:ON)
52-
f_name = parse_name
60+
f_name = if !at?(:ON)
61+
parse_name
62+
end
5363
expect_token :ON
5464
f_type = parse_type_name
5565
directives = parse_directives
@@ -82,7 +92,8 @@ def definition
8292
expect_token(:COLON)
8393
var_type = self.type
8494
default_value = if at?(:EQUALS)
85-
self.default_value
95+
advance_token
96+
value
8697
end
8798

8899
defs << Nodes::VariableDefinition.new(pos: loc, name: var_name, type: var_type, default_value: default_value)
@@ -151,7 +162,7 @@ def definition
151162
input_fields_definition = parse_input_object_field_definitions
152163
InputObjectTypeExtension.new(pos: loc, name: name, directives: directives, fields: input_fields_definition)
153164
else
154-
expect_token :SOME_TYPE_EXTENSION
165+
expect_one_of([:SCALAR, :TYPE, :ENUM, :INPUT, :UNION, :INTERFACE])
155166
end
156167
else
157168
desc = at?(:STRING) ? string_value : nil
@@ -177,7 +188,7 @@ def definition
177188
expect_token(:COLON)
178189
subscription = parse_name
179190
else
180-
expect_token(:SOME_ROOT_TYPE_NAME)
191+
expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION])
181192
end
182193
end
183194
expect_token :RCURLY
@@ -188,13 +199,18 @@ def definition
188199
expect_token :DIR_SIGN
189200
name = parse_name
190201
arguments_definition = parse_argument_definitions
202+
repeatable = if at?(:REPEATABLE)
203+
advance_token
204+
true
205+
else
206+
fasle
207+
end
191208
expect_token :ON
192209
directive_locations = [DirectiveLocation.new(pos: pos, name: parse_name)]
193210
while at?(:PIPE)
194211
advance_token
195212
directive_locations << DirectiveLocation.new(pos: pos, name: parse_name)
196213
end
197-
repeatable = false # TODO parse this
198214
DirectiveDefinition.new(pos: loc, description: desc, name: name, arguments: arguments_definition, locations: directive_locations, repeatable: repeatable)
199215
when :TYPE
200216
loc = pos
@@ -241,7 +257,7 @@ def definition
241257
input_fields_definition = parse_input_object_field_definitions
242258
InputObjectTypeDefinition.new(pos: loc, description: desc, name: name, directives: directives, fields: input_fields_definition)
243259
else
244-
expect_token(:SOME_ROOT_DEFINITION)
260+
expect_one_of([:SCALAR, :TYPE, :ENUM, :INPUT, :UNION, :INTERFACE])
245261
end
246262
end
247263
end
@@ -294,8 +310,8 @@ def parse_union_members
294310

295311
def parse_implements
296312
if at?(:IMPLEMENTS)
297-
expect_token :IMPLEMENTS
298-
list = [parse_type_name]
313+
advance_token
314+
list = []
299315
while true
300316
advance_token if at?(:AMP)
301317
break unless at?(:IDENTIFIER)
@@ -327,7 +343,7 @@ def parse_field_definitions
327343

328344
def parse_argument_definitions
329345
if at?(:LPAREN)
330-
expect_token :LPAREN
346+
advance_token
331347
list = []
332348
while !at?(:RPAREN)
333349
list << parse_input_value_definition
@@ -346,7 +362,7 @@ def parse_input_value_definition
346362
expect_token :COLON
347363
type = self.type
348364
default_value = if at?(:EQUALS)
349-
expect_token(:EQUALS)
365+
advance_token
350366
value
351367
else
352368
nil
@@ -411,17 +427,15 @@ def selection_set
411427
directives = parse_directives
412428

413429
Nodes::InlineFragment.new(pos: loc, type: if_type, directives: directives, selections: selection_set)
414-
when :IDENTIFIER
430+
else
415431
loc = pos
416-
name = parse_name
432+
name = parse_name_without_on
417433
directives = parse_directives
418434

419435
# Can this ever happen?
420436
# expect_token(:IDENTIFIER) if at?(:ON)
421437

422438
FragmentSpread.new(pos: loc, name: name, directives: directives)
423-
else
424-
expect_token(:FRAGMENT_SPREAD)
425439
end
426440
else
427441
loc = pos
@@ -431,7 +445,7 @@ def selection_set
431445

432446
if at?(:COLON)
433447
advance_token
434-
field_alias = parse_name
448+
field_alias = name
435449
name = parse_name
436450
end
437451

@@ -450,17 +464,76 @@ def parse_name
450464
case token_name
451465
when :IDENTIFIER
452466
expect_token_value(:IDENTIFIER)
467+
when :SCHEMA
468+
advance_token
469+
"schema"
470+
when :SCALAR
471+
advance_token
472+
"scalar"
473+
when :IMPLEMENTS
474+
advance_token
475+
"implements"
476+
when :INTERFACE
477+
advance_token
478+
"interface"
479+
when :UNION
480+
advance_token
481+
"union"
482+
when :ENUM
483+
advance_token
484+
"enum"
485+
when :INPUT
486+
advance_token
487+
"input"
488+
when :DIRECTIVE
489+
advance_token
490+
"directive"
453491
when :TYPE
454492
advance_token
455493
"type"
456494
when :QUERY
457495
advance_token
458496
"query"
459-
when :INPUT
497+
when :MUTATION
460498
advance_token
461-
"input"
499+
"mutation"
500+
when :SUBSCRIPTION
501+
advance_token
502+
"subscription"
503+
when :TRUE
504+
advance_token
505+
"true"
506+
when :FALSE
507+
advance_token
508+
"false"
509+
when :FRAGMENT
510+
advance_token
511+
"fragment"
512+
when :REPEATABLE
513+
advance_token
514+
"repeatable"
515+
when :NULL
516+
advance_token
517+
"null"
518+
else
519+
expect_token(:IDENTIFIER)
520+
end
521+
end
522+
523+
def parse_name_without_on
524+
if at?(:ON)
525+
expect_token(:IDENTIFIER)
462526
else
527+
parse_name
528+
end
529+
end
530+
531+
# Any identifier, but not true, false, or null
532+
def parse_enum_name
533+
if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
463534
expect_token(:IDENTIFIER)
535+
else
536+
parse_name
464537
end
465538
end
466539

@@ -473,7 +546,7 @@ def parse_directives
473546
dirs = []
474547
while at?(:DIR_SIGN)
475548
loc = pos
476-
expect_token(:DIR_SIGN)
549+
advance_token
477550
name = parse_name
478551
arguments = parse_arguments
479552

@@ -562,11 +635,27 @@ def at?(expected_token_name)
562635

563636
def expect_token(expected_token_name)
564637
unless @token_name == expected_token_name
565-
raise "TODO nice error for Expected token #{expected_token_name}, actual: #{token_name.inspect} #{@lexer.token_value} line: #{@lexer.line} / pos: #{@lexer.pos}"
638+
raise_parse_error("Expected #{expected_token_name}, actual: #{token_name} (#{@lexer.token_value.inspect})")
566639
end
567640
advance_token
568641
end
569642

643+
def expect_one_of(token_names)
644+
raise_parse_error("Expected one of #{token_names.join(", ")}, actual: #{token_name} (#{@lexer.token_value.inspect})")
645+
end
646+
647+
def raise_parse_error(message)
648+
message += " at [#{@lexer.line_number}, #{@lexer.column_number}]"
649+
raise GraphQL::ParseError.new(
650+
message,
651+
@lexer.line_number,
652+
@lexer.column_number,
653+
@graphql_str,
654+
filename: @filename,
655+
)
656+
657+
end
658+
570659
# Only use when we care about the expected token's value
571660
def expect_token_value(tok)
572661
token_value = @lexer.token_value
@@ -624,15 +713,15 @@ def advance
624713
@scanner[1] ? :FLOAT : :INT
625714
when ByteFor::ELLIPSIS
626715
if @string.getbyte(@pos + 1) != 46 || @string.getbyte(@pos + 2) != 46
627-
raise "TODO raise a nice error for a malformed ellipsis"
716+
raise_parse_error("Expected `...`, actual: #{@string[@pos..@pos + 2].inspect}")
628717
end
629718
@scanner.pos += 3
630719
:ELLIPSIS
631720
when ByteFor::STRING
632721
if @scanner.skip(BLOCK_STRING_REGEXP) || @scanner.skip(QUOTED_STRING_REGEXP)
633722
:STRING
634723
else
635-
raise "TODO Raise a nice error for a badly-formatted string"
724+
raise_parse_error("Expected string or block string, but it was malformed")
636725
end
637726
else
638727
@scanner.pos += 1
@@ -643,7 +732,7 @@ def advance
643732
def token_value
644733
@string.byteslice(@scanner.pos - @scanner.matched_size, @scanner.matched_size)
645734
rescue StandardError => err
646-
"(token_value failed: #{err.class}: #{err.message})"
735+
raise GraphQL::Error, "(token_value failed: #{err.class}: #{err.message})"
647736
end
648737

649738
def string_value
@@ -656,23 +745,33 @@ def string_value
656745
end
657746

658747
if !str.valid_encoding? || !str.match?(Language::Lexer::VALID_STRING)
659-
raise "TODO Bad Unicode escape"
748+
raise_parse_error("Bad unicode escape in #{str.inspect}")
660749
else
661750
GraphQL::Language::Lexer.replace_escaped_characters_in_place(str)
662751

663752
if !str.valid_encoding?
664-
raise "TODO Bad Unicode escape"
753+
raise_parse_error("Bad unicode escape in #{str.inspect}")
665754
else
666755
str
667756
end
668757
end
669758
end
670759

671-
def line
760+
def line_number
672761
@scanner.string[0, @scanner.pos].count("\n") + 1
673762
end
674763

675-
IGNORE_REGEXP = /[, \c\r\n\t]+/
764+
def column_number
765+
@scanner.string[0, @scanner.pos].split("\n").last.length - token_value.length + 1
766+
end
767+
768+
# IGNORE_REGEXP = /[, \c\r\n\t]+/
769+
IGNORE_REGEXP = %r{
770+
(?:
771+
[, \c\r\n\t]+ |
772+
\#.*$
773+
)*
774+
}x
676775
IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
677776
INT_REGEXP = /[-]?(?:[0]|[1-9][0-9]*)/
678777
FLOAT_DECIMAL_REGEXP = /[.][0-9]+/

spec/graphql/language/parser_spec.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,21 @@
355355
query = GraphQL::Query.new(schema, "{ t: __typename }")
356356
subject.parse("{ t: __typename }", trace: query.current_trace)
357357
traces = TestTracing.traces
358-
assert_equal 2, traces.length
358+
expected_traces = if GraphQL.default_parser == GraphQL::Language::RecursiveDescentParser
359+
1
360+
else
361+
2
362+
end
363+
assert_equal expected_traces, traces.length
359364
lex_trace, parse_trace = traces
360365

361-
assert_equal "{ t: __typename }", lex_trace[:query_string]
362-
assert_equal "lex", lex_trace[:key]
363-
assert_instance_of Array, lex_trace[:result]
366+
if GraphQL.default_parser == GraphQL::Language::Parser
367+
assert_equal "{ t: __typename }", lex_trace[:query_string]
368+
assert_equal "lex", lex_trace[:key]
369+
assert_instance_of Array, lex_trace[:result]
370+
else
371+
parse_trace = lex_trace
372+
end
364373

365374
assert_equal "{ t: __typename }", parse_trace[:query_string]
366375
assert_equal "parse", parse_trace[:key]

0 commit comments

Comments
 (0)