graphql 2.0.30 → 2.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +3 -0
  6. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  7. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  8. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  9. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  10. data/lib/generators/graphql/templates/base_field.erb +2 -0
  11. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  13. data/lib/generators/graphql/templates/base_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  15. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  16. data/lib/generators/graphql/templates/base_union.erb +2 -0
  17. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  18. data/lib/generators/graphql/templates/loader.erb +2 -0
  19. data/lib/generators/graphql/templates/mutation.erb +2 -0
  20. data/lib/generators/graphql/templates/node_type.erb +2 -0
  21. data/lib/generators/graphql/templates/query_type.erb +2 -0
  22. data/lib/generators/graphql/templates/schema.erb +5 -0
  23. data/lib/graphql/analysis/analyzer.rb +89 -0
  24. data/lib/graphql/analysis/field_usage.rb +82 -0
  25. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  26. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  27. data/lib/graphql/analysis/query_complexity.rb +183 -0
  28. data/lib/graphql/analysis/query_depth.rb +58 -0
  29. data/lib/graphql/analysis/visitor.rb +282 -0
  30. data/lib/graphql/analysis.rb +92 -1
  31. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  32. data/lib/graphql/backtrace/trace.rb +12 -15
  33. data/lib/graphql/coercion_error.rb +1 -9
  34. data/lib/graphql/dataloader/async_dataloader.rb +88 -0
  35. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  36. data/lib/graphql/dataloader/request.rb +5 -0
  37. data/lib/graphql/dataloader/source.rb +11 -3
  38. data/lib/graphql/dataloader.rb +112 -142
  39. data/lib/graphql/duration_encoding_error.rb +16 -0
  40. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  41. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
  42. data/lib/graphql/execution/interpreter/runtime.rb +163 -365
  43. data/lib/graphql/execution/interpreter.rb +92 -158
  44. data/lib/graphql/execution/lookahead.rb +88 -21
  45. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  46. data/lib/graphql/introspection/entry_points.rb +11 -5
  47. data/lib/graphql/introspection/schema_type.rb +3 -1
  48. data/lib/graphql/language/block_string.rb +34 -18
  49. data/lib/graphql/language/definition_slice.rb +1 -1
  50. data/lib/graphql/language/document_from_schema_definition.rb +38 -38
  51. data/lib/graphql/language/lexer.rb +305 -193
  52. data/lib/graphql/language/nodes.rb +113 -66
  53. data/lib/graphql/language/parser.rb +787 -1986
  54. data/lib/graphql/language/printer.rb +303 -146
  55. data/lib/graphql/language/sanitized_printer.rb +20 -22
  56. data/lib/graphql/language/static_visitor.rb +167 -0
  57. data/lib/graphql/language/visitor.rb +20 -81
  58. data/lib/graphql/language.rb +61 -0
  59. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  60. data/lib/graphql/pagination/array_connection.rb +6 -6
  61. data/lib/graphql/pagination/connection.rb +28 -1
  62. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  63. data/lib/graphql/query/context/scoped_context.rb +101 -0
  64. data/lib/graphql/query/context.rb +66 -131
  65. data/lib/graphql/query/null_context.rb +4 -11
  66. data/lib/graphql/query/validation_pipeline.rb +4 -4
  67. data/lib/graphql/query/variables.rb +3 -3
  68. data/lib/graphql/query.rb +17 -26
  69. data/lib/graphql/railtie.rb +9 -6
  70. data/lib/graphql/rake_task.rb +3 -12
  71. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  72. data/lib/graphql/schema/addition.rb +21 -11
  73. data/lib/graphql/schema/argument.rb +43 -8
  74. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  75. data/lib/graphql/schema/build_from_definition.rb +9 -12
  76. data/lib/graphql/schema/directive/one_of.rb +12 -0
  77. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  78. data/lib/graphql/schema/directive.rb +3 -1
  79. data/lib/graphql/schema/enum.rb +3 -3
  80. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  81. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  82. data/lib/graphql/schema/field.rb +49 -35
  83. data/lib/graphql/schema/has_single_input_argument.rb +157 -0
  84. data/lib/graphql/schema/input_object.rb +4 -4
  85. data/lib/graphql/schema/interface.rb +10 -10
  86. data/lib/graphql/schema/introspection_system.rb +4 -2
  87. data/lib/graphql/schema/late_bound_type.rb +4 -0
  88. data/lib/graphql/schema/list.rb +2 -2
  89. data/lib/graphql/schema/loader.rb +2 -3
  90. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  91. data/lib/graphql/schema/member/has_arguments.rb +63 -73
  92. data/lib/graphql/schema/member/has_directives.rb +1 -1
  93. data/lib/graphql/schema/member/has_fields.rb +8 -5
  94. data/lib/graphql/schema/member/has_interfaces.rb +23 -9
  95. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  96. data/lib/graphql/schema/member/scoped.rb +19 -0
  97. data/lib/graphql/schema/member/type_system_helpers.rb +1 -2
  98. data/lib/graphql/schema/member/validates_input.rb +3 -3
  99. data/lib/graphql/schema/mutation.rb +7 -0
  100. data/lib/graphql/schema/object.rb +8 -0
  101. data/lib/graphql/schema/printer.rb +8 -7
  102. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  103. data/lib/graphql/schema/resolver.rb +27 -13
  104. data/lib/graphql/schema/scalar.rb +3 -3
  105. data/lib/graphql/schema/subscription.rb +11 -4
  106. data/lib/graphql/schema/union.rb +1 -1
  107. data/lib/graphql/schema/unique_within_type.rb +1 -1
  108. data/lib/graphql/schema/warden.rb +96 -95
  109. data/lib/graphql/schema.rb +323 -102
  110. data/lib/graphql/static_validation/all_rules.rb +1 -1
  111. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  112. data/lib/graphql/static_validation/literal_validator.rb +2 -3
  113. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  114. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  115. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +2 -2
  116. data/lib/graphql/static_validation/validation_context.rb +5 -5
  117. data/lib/graphql/static_validation/validator.rb +3 -0
  118. data/lib/graphql/static_validation.rb +0 -1
  119. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  120. data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
  121. data/lib/graphql/subscriptions/event.rb +8 -2
  122. data/lib/graphql/subscriptions/serialize.rb +2 -0
  123. data/lib/graphql/subscriptions.rb +15 -13
  124. data/lib/graphql/testing/helpers.rb +151 -0
  125. data/lib/graphql/testing.rb +2 -0
  126. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  127. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  128. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  129. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  130. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  131. data/lib/graphql/tracing/prometheus_trace.rb +9 -9
  132. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  133. data/lib/graphql/tracing/trace.rb +1 -0
  134. data/lib/graphql/tracing.rb +3 -1
  135. data/lib/graphql/type_kinds.rb +1 -1
  136. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  137. data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
  138. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  139. data/lib/graphql/types.rb +1 -0
  140. data/lib/graphql/version.rb +1 -1
  141. data/lib/graphql.rb +13 -13
  142. data/readme.md +12 -2
  143. metadata +33 -26
  144. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  145. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  146. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  147. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  148. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  149. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  150. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  151. data/lib/graphql/analysis/ast.rb +0 -81
  152. data/lib/graphql/deprecation.rb +0 -9
  153. data/lib/graphql/filter.rb +0 -59
  154. data/lib/graphql/language/parser.y +0 -560
  155. data/lib/graphql/schema/base_64_bp.rb +0 -26
  156. data/lib/graphql/static_validation/type_stack.rb +0 -216
  157. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -1,39 +1,246 @@
1
1
  # frozen_string_literal: true
2
-
3
- require "strscan"
4
-
5
2
  module GraphQL
6
3
  module Language
4
+
7
5
  class Lexer
8
- IDENTIFIER = /[_A-Za-z][_0-9A-Za-z]*/
9
- NEWLINE = /[\c\r\n]/
10
- BLANK = /[, \t]+/
11
- COMMENT = /#[^\n\r]*/
12
- INT = /[-]?(?:[0]|[1-9][0-9]*)/
13
- FLOAT_DECIMAL = /[.][0-9]+/
14
- FLOAT_EXP = /[eE][+-]?[0-9]+/
15
- FLOAT = /#{INT}(#{FLOAT_DECIMAL}#{FLOAT_EXP}|#{FLOAT_DECIMAL}|#{FLOAT_EXP})/
16
-
17
- module Literals
18
- ON = /on\b/
19
- FRAGMENT = /fragment\b/
20
- TRUE = /true\b/
21
- FALSE = /false\b/
22
- NULL = /null\b/
23
- QUERY = /query\b/
24
- MUTATION = /mutation\b/
25
- SUBSCRIPTION = /subscription\b/
26
- SCHEMA = /schema\b/
27
- SCALAR = /scalar\b/
28
- TYPE = /type\b/
29
- EXTEND = /extend\b/
30
- IMPLEMENTS = /implements\b/
31
- INTERFACE = /interface\b/
32
- UNION = /union\b/
33
- ENUM = /enum\b/
34
- INPUT = /input\b/
35
- DIRECTIVE = /directive\b/
36
- REPEATABLE = /repeatable\b/
6
+ def initialize(graphql_str, filename: nil, max_tokens: nil)
7
+ if !(graphql_str.encoding == Encoding::UTF_8 || graphql_str.ascii_only?)
8
+ graphql_str = graphql_str.dup.force_encoding(Encoding::UTF_8)
9
+ end
10
+ @string = graphql_str
11
+ @filename = filename
12
+ @scanner = StringScanner.new(graphql_str)
13
+ @pos = nil
14
+ @max_tokens = max_tokens || Float::INFINITY
15
+ @tokens_count = 0
16
+ end
17
+
18
+ def eos?
19
+ @scanner.eos?
20
+ end
21
+
22
+ attr_reader :pos
23
+
24
+ def advance
25
+ @scanner.skip(IGNORE_REGEXP)
26
+ return false if @scanner.eos?
27
+ @tokens_count += 1
28
+ if @tokens_count > @max_tokens
29
+ raise_parse_error("This query is too large to execute.")
30
+ end
31
+ @pos = @scanner.pos
32
+ next_byte = @string.getbyte(@pos)
33
+ next_byte_is_for = FIRST_BYTES[next_byte]
34
+ case next_byte_is_for
35
+ when ByteFor::PUNCTUATION
36
+ @scanner.pos += 1
37
+ PUNCTUATION_NAME_FOR_BYTE[next_byte]
38
+ when ByteFor::NAME
39
+ if len = @scanner.skip(KEYWORD_REGEXP)
40
+ case len
41
+ when 2
42
+ :ON
43
+ when 12
44
+ :SUBSCRIPTION
45
+ else
46
+ pos = @pos
47
+
48
+ # Use bytes 2 and 3 as a unique identifier for this keyword
49
+ bytes = (@string.getbyte(pos + 2) << 8) | @string.getbyte(pos + 1)
50
+ KEYWORD_BY_TWO_BYTES[_hash(bytes)]
51
+ end
52
+ else
53
+ @scanner.skip(IDENTIFIER_REGEXP)
54
+ :IDENTIFIER
55
+ end
56
+ when ByteFor::IDENTIFIER
57
+ @scanner.skip(IDENTIFIER_REGEXP)
58
+ :IDENTIFIER
59
+ when ByteFor::NUMBER
60
+ @scanner.skip(NUMERIC_REGEXP)
61
+
62
+ if GraphQL.reject_numbers_followed_by_names
63
+ new_pos = @scanner.pos
64
+ peek_byte = @string.getbyte(new_pos)
65
+ next_first_byte = FIRST_BYTES[peek_byte]
66
+ if next_first_byte == ByteFor::NAME || next_first_byte == ByteFor::IDENTIFIER
67
+ number_part = token_value
68
+ name_part = @scanner.scan(IDENTIFIER_REGEXP)
69
+ raise_parse_error("Name after number is not allowed (in `#{number_part}#{name_part}`)")
70
+ end
71
+ end
72
+ # Check for a matched decimal:
73
+ @scanner[1] ? :FLOAT : :INT
74
+ when ByteFor::ELLIPSIS
75
+ if @string.getbyte(@pos + 1) != 46 || @string.getbyte(@pos + 2) != 46
76
+ raise_parse_error("Expected `...`, actual: #{@string[@pos..@pos + 2].inspect}")
77
+ end
78
+ @scanner.pos += 3
79
+ :ELLIPSIS
80
+ when ByteFor::STRING
81
+ if @scanner.skip(BLOCK_STRING_REGEXP) || @scanner.skip(QUOTED_STRING_REGEXP)
82
+ :STRING
83
+ else
84
+ raise_parse_error("Expected string or block string, but it was malformed")
85
+ end
86
+ else
87
+ @scanner.pos += 1
88
+ :UNKNOWN_CHAR
89
+ end
90
+ rescue ArgumentError => err
91
+ if err.message == "invalid byte sequence in UTF-8"
92
+ raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil)
93
+ end
94
+ end
95
+
96
+ def token_value
97
+ @string.byteslice(@scanner.pos - @scanner.matched_size, @scanner.matched_size)
98
+ rescue StandardError => err
99
+ raise GraphQL::Error, "(token_value failed: #{err.class}: #{err.message})"
100
+ end
101
+
102
+ def debug_token_value(token_name)
103
+ if token_name && Lexer::Punctuation.const_defined?(token_name)
104
+ Lexer::Punctuation.const_get(token_name)
105
+ elsif token_name == :ELLIPSIS
106
+ "..."
107
+ elsif token_name == :STRING
108
+ string_value
109
+ elsif @scanner.matched_size.nil?
110
+ @scanner.peek(1)
111
+ else
112
+ token_value
113
+ end
114
+ end
115
+
116
+ ESCAPES = /\\["\\\/bfnrt]/
117
+ ESCAPES_REPLACE = {
118
+ '\\"' => '"',
119
+ "\\\\" => "\\",
120
+ "\\/" => '/',
121
+ "\\b" => "\b",
122
+ "\\f" => "\f",
123
+ "\\n" => "\n",
124
+ "\\r" => "\r",
125
+ "\\t" => "\t",
126
+ }
127
+ UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
128
+ VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
129
+ ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
130
+
131
+ def string_value
132
+ str = token_value
133
+ is_block = str.start_with?('"""')
134
+ if is_block
135
+ str.gsub!(/\A"""|"""\z/, '')
136
+ return Language::BlockString.trim_whitespace(str)
137
+ else
138
+ str.gsub!(/\A"|"\z/, '')
139
+
140
+ if !str.valid_encoding? || !str.match?(VALID_STRING)
141
+ raise_parse_error("Bad unicode escape in #{str.inspect}")
142
+ else
143
+ Lexer.replace_escaped_characters_in_place(str)
144
+
145
+ if !str.valid_encoding?
146
+ raise_parse_error("Bad unicode escape in #{str.inspect}")
147
+ else
148
+ str
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ def line_number
155
+ @scanner.string[0..@pos].count("\n") + 1
156
+ end
157
+
158
+ def column_number
159
+ @scanner.string[0..@pos].split("\n").last.length
160
+ end
161
+
162
+ def raise_parse_error(message, line = line_number, col = column_number)
163
+ raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename)
164
+ end
165
+
166
+ IGNORE_REGEXP = %r{
167
+ (?:
168
+ [, \c\r\n\t]+ |
169
+ \#.*$
170
+ )*
171
+ }x
172
+ IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
173
+ INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
174
+ FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
175
+ FLOAT_EXP_REGEXP = /[eE][+-]?[0-9]+/
176
+ # TODO: FLOAT_EXP_REGEXP should not be allowed to follow INT_REGEXP, integers are not allowed to have exponent parts.
177
+ NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/
178
+
179
+ KEYWORDS = [
180
+ "on",
181
+ "fragment",
182
+ "true",
183
+ "false",
184
+ "null",
185
+ "query",
186
+ "mutation",
187
+ "subscription",
188
+ "schema",
189
+ "scalar",
190
+ "type",
191
+ "extend",
192
+ "implements",
193
+ "interface",
194
+ "union",
195
+ "enum",
196
+ "input",
197
+ "directive",
198
+ "repeatable"
199
+ ].freeze
200
+
201
+ KEYWORD_REGEXP = /#{Regexp.union(KEYWORDS.sort)}\b/
202
+ KEYWORD_BY_TWO_BYTES = [
203
+ :INTERFACE,
204
+ :MUTATION,
205
+ :EXTEND,
206
+ :FALSE,
207
+ :ENUM,
208
+ :TRUE,
209
+ :NULL,
210
+ nil,
211
+ nil,
212
+ nil,
213
+ nil,
214
+ nil,
215
+ nil,
216
+ nil,
217
+ :QUERY,
218
+ nil,
219
+ nil,
220
+ :REPEATABLE,
221
+ :IMPLEMENTS,
222
+ :INPUT,
223
+ :TYPE,
224
+ :SCHEMA,
225
+ nil,
226
+ nil,
227
+ nil,
228
+ :DIRECTIVE,
229
+ :UNION,
230
+ nil,
231
+ nil,
232
+ :SCALAR,
233
+ nil,
234
+ :FRAGMENT
235
+ ]
236
+
237
+ # This produces a unique integer for bytes 2 and 3 of each keyword string
238
+ # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
239
+ def _hash key
240
+ (key * 18592990) >> 27 & 0x1f
241
+ end
242
+
243
+ module Punctuation
37
244
  LCURLY = '{'
38
245
  RCURLY = '}'
39
246
  LPAREN = '('
@@ -43,36 +250,30 @@ module GraphQL
43
250
  COLON = ':'
44
251
  VAR_SIGN = '$'
45
252
  DIR_SIGN = '@'
46
- ELLIPSIS = '...'
47
253
  EQUALS = '='
48
254
  BANG = '!'
49
255
  PIPE = '|'
50
256
  AMP = '&'
51
257
  end
52
258
 
53
- include Literals
259
+ # A sparse array mapping the bytes for each punctuation
260
+ # to a symbol name for that punctuation
261
+ PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
262
+ punct = Punctuation.const_get(name)
263
+ arr[punct.ord] = name
264
+ }
54
265
 
55
266
  QUOTE = '"'
56
267
  UNICODE_DIGIT = /[0-9A-Za-z]/
57
268
  FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/
58
- N_DIGIT_UNICODE = %r{#{LCURLY}#{UNICODE_DIGIT}{4,}#{RCURLY}}x
269
+ N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{4,}#{Punctuation::RCURLY}}x
59
270
  UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})}
60
- # # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value
61
271
  STRING_ESCAPE = %r{[\\][\\/bfnrt]}
62
272
  BLOCK_QUOTE = '"""'
63
273
  ESCAPED_QUOTE = /\\"/;
64
- STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
65
-
66
- LIT_NAME_LUT = Literals.constants.each_with_object({}) { |n, o|
67
- key = Literals.const_get(n)
68
- key = key.is_a?(Regexp) ? key.source.gsub(/(\\b|\\)/, '') : key
69
- o[key] = n
70
- }
71
-
72
- LIT = Regexp.union(Literals.constants.map { |n| Literals.const_get(n) })
73
-
74
- QUOTED_STRING = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
75
- BLOCK_STRING = %r{
274
+ STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\\n\r]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
275
+ QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
276
+ BLOCK_STRING_REGEXP = %r{
76
277
  #{BLOCK_QUOTE}
77
278
  (?: [^"\\] | # Any characters that aren't a quote or slash
78
279
  (?<!") ["]{1,2} (?!") | # Any quotes that don't have quotes next to them
@@ -84,169 +285,80 @@ module GraphQL
84
285
  #{BLOCK_QUOTE}
85
286
  }xm
86
287
 
87
- # # catch-all for anything else. must be at the bottom for precedence.
88
- UNKNOWN_CHAR = /./
89
-
90
- def initialize(value)
91
- @line = 1
92
- @col = 1
93
- @previous_token = nil
94
-
95
- @scan = scanner value
288
+ # Use this array to check, for a given byte that will start a token,
289
+ # what kind of token might it start?
290
+ FIRST_BYTES = Array.new(255)
291
+
292
+ module ByteFor
293
+ NUMBER = 0 # int or float
294
+ NAME = 1 # identifier or keyword
295
+ STRING = 2
296
+ ELLIPSIS = 3
297
+ IDENTIFIER = 4 # identifier, *not* a keyword
298
+ PUNCTUATION = 5
96
299
  end
97
300
 
98
- class BadEncoding < Lexer # :nodoc:
99
- def scanner(value)
100
- [emit(:BAD_UNICODE_ESCAPE, 0, 0, value)]
101
- end
102
-
103
- def next_token
104
- @scan.pop
105
- end
301
+ (0..9).each { |i| FIRST_BYTES[i.to_s.ord] = ByteFor::NUMBER }
302
+ FIRST_BYTES["-".ord] = ByteFor::NUMBER
303
+ # Some of these may be overwritten below, if keywords start with the same character
304
+ ("A".."Z").each { |char| FIRST_BYTES[char.ord] = ByteFor::IDENTIFIER }
305
+ ("a".."z").each { |char| FIRST_BYTES[char.ord] = ByteFor::IDENTIFIER }
306
+ FIRST_BYTES['_'.ord] = ByteFor::IDENTIFIER
307
+ FIRST_BYTES['.'.ord] = ByteFor::ELLIPSIS
308
+ FIRST_BYTES['"'.ord] = ByteFor::STRING
309
+ KEYWORDS.each { |kw| FIRST_BYTES[kw.getbyte(0)] = ByteFor::NAME }
310
+ Punctuation.constants.each do |punct_name|
311
+ punct = Punctuation.const_get(punct_name)
312
+ FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
106
313
  end
107
314
 
108
- def self.tokenize(string)
109
- value = string.dup.force_encoding(Encoding::UTF_8)
110
-
111
- scanner = if value.valid_encoding?
112
- new value
113
- else
114
- BadEncoding.new value
115
- end
116
-
117
- toks = []
118
-
119
- while tok = scanner.next_token
120
- toks << tok
121
- end
122
-
123
- toks
124
- end
125
-
126
- def next_token
127
- return if @scan.eos?
128
-
129
- pos = @scan.pos
130
-
131
- case
132
- when str = @scan.scan(FLOAT) then emit(:FLOAT, pos, @scan.pos, str)
133
- when str = @scan.scan(INT) then emit(:INT, pos, @scan.pos, str)
134
- when str = @scan.scan(LIT) then emit(LIT_NAME_LUT[str], pos, @scan.pos, -str)
135
- when str = @scan.scan(IDENTIFIER) then emit(:IDENTIFIER, pos, @scan.pos, str)
136
- when str = @scan.scan(BLOCK_STRING) then emit_block(pos, @scan.pos, str.gsub(/\A#{BLOCK_QUOTE}|#{BLOCK_QUOTE}\z/, ''))
137
- when str = @scan.scan(QUOTED_STRING) then emit_string(pos, @scan.pos, str.gsub(/^"|"$/, ''))
138
- when str = @scan.scan(COMMENT) then record_comment(pos, @scan.pos, str)
139
- when str = @scan.scan(NEWLINE)
140
- @line += 1
141
- @col = 1
142
- next_token
143
- when @scan.scan(BLANK)
144
- @col += @scan.pos - pos
145
- next_token
146
- when str = @scan.scan(UNKNOWN_CHAR) then emit(:UNKNOWN_CHAR, pos, @scan.pos, str)
147
- else
148
- # This should never happen since `UNKNOWN_CHAR` ensures we make progress
149
- raise "Unknown string?"
150
- end
151
- end
152
-
153
- def emit(token_name, ts, te, token_value)
154
- token = [
155
- token_name,
156
- @line,
157
- @col,
158
- token_value,
159
- @previous_token,
160
- ]
161
- @previous_token = token
162
- # Bump the column counter for the next token
163
- @col += te - ts
164
- token
165
- end
166
315
 
167
316
  # Replace any escaped unicode or whitespace with the _actual_ characters
168
317
  # To avoid allocating more strings, this modifies the string passed into it
169
318
  def self.replace_escaped_characters_in_place(raw_string)
170
- raw_string.gsub!(ESCAPES, ESCAPES_REPLACE)
171
- raw_string.gsub!(UTF_8) do |_matched_str|
172
- codepoint_1 = ($1 || $2).to_i(16)
173
- codepoint_2 = $3
174
-
175
- if codepoint_2
176
- codepoint_2 = codepoint_2.to_i(16)
177
- if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
178
- (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
179
- # A surrogate pair
180
- combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
181
- [combined].pack('U'.freeze)
319
+ raw_string.gsub!(ESCAPED) do |matched_str|
320
+ if (point_str_1 = $1 || $2)
321
+ codepoint_1 = point_str_1.to_i(16)
322
+ if (codepoint_2 = $3)
323
+ codepoint_2 = codepoint_2.to_i(16)
324
+ if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
325
+ (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
326
+ # A surrogate pair
327
+ combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
328
+ [combined].pack('U'.freeze)
329
+ else
330
+ # Two separate code points
331
+ [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
332
+ end
182
333
  else
183
- # Two separate code points
184
- [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
334
+ [codepoint_1].pack('U'.freeze)
185
335
  end
186
336
  else
187
- [codepoint_1].pack('U'.freeze)
337
+ ESCAPES_REPLACE[matched_str]
188
338
  end
189
339
  end
190
340
  nil
191
341
  end
192
342
 
193
- def record_comment(ts, te, str)
194
- token = [
195
- :COMMENT,
196
- @line,
197
- @col,
198
- str,
199
- @previous_token,
200
- ]
201
-
202
- @previous_token = token
203
-
204
- @col += te - ts
205
- next_token
206
- end
207
-
208
- ESCAPES = /\\["\\\/bfnrt]/
209
- ESCAPES_REPLACE = {
210
- '\\"' => '"',
211
- "\\\\" => "\\",
212
- "\\/" => '/',
213
- "\\b" => "\b",
214
- "\\f" => "\f",
215
- "\\n" => "\n",
216
- "\\r" => "\r",
217
- "\\t" => "\t",
218
- }
219
- UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
220
- VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
221
-
222
- def emit_block(ts, te, value)
223
- line_incr = value.count("\n")
224
- value = GraphQL::Language::BlockString.trim_whitespace(value)
225
- tok = emit_string(ts, te, value)
226
- @line += line_incr
227
- tok
228
- end
229
-
230
- def emit_string(ts, te, value)
231
- if !value.valid_encoding? || !value.match?(VALID_STRING)
232
- emit(:BAD_UNICODE_ESCAPE, ts, te, value)
233
- else
234
- self.class.replace_escaped_characters_in_place(value)
235
-
236
- if !value.valid_encoding?
237
- emit(:BAD_UNICODE_ESCAPE, ts, te, value)
238
- else
239
- emit(:STRING, ts, te, value)
240
- end
343
+ # This is not used during parsing because the parser
344
+ # doesn't actually need tokens.
345
+ def self.tokenize(string)
346
+ lexer = GraphQL::Language::Lexer.new(string)
347
+ tokens = []
348
+ prev_token = nil
349
+ while (token_name = lexer.advance)
350
+ new_token = [
351
+ token_name,
352
+ lexer.line_number,
353
+ lexer.column_number,
354
+ lexer.debug_token_value(token_name),
355
+ prev_token,
356
+ ]
357
+ tokens << new_token
358
+ prev_token = new_token
241
359
  end
360
+ tokens
242
361
  end
243
-
244
- private
245
-
246
- def scanner(value)
247
- StringScanner.new value
248
- end
249
-
250
362
  end
251
363
  end
252
364
  end