graphql 1.8.0.pre9 → 1.8.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/argument.rb +1 -0
  3. data/lib/graphql/base_type.rb +2 -0
  4. data/lib/graphql/compatibility/query_parser_specification.rb +110 -0
  5. data/lib/graphql/deprecated_dsl.rb +15 -3
  6. data/lib/graphql/directive.rb +1 -0
  7. data/lib/graphql/enum_type.rb +2 -0
  8. data/lib/graphql/execution/multiplex.rb +1 -1
  9. data/lib/graphql/field.rb +2 -0
  10. data/lib/graphql/introspection/entry_points.rb +2 -2
  11. data/lib/graphql/introspection/schema_field.rb +1 -1
  12. data/lib/graphql/introspection/type_by_name_field.rb +1 -1
  13. data/lib/graphql/language/parser.rb +25 -25
  14. data/lib/graphql/language/parser.y +7 -7
  15. data/lib/graphql/query/arguments.rb +12 -0
  16. data/lib/graphql/relay/mutation/instrumentation.rb +1 -1
  17. data/lib/graphql/relay/mutation/resolve.rb +5 -1
  18. data/lib/graphql/schema.rb +5 -1
  19. data/lib/graphql/schema/argument.rb +1 -0
  20. data/lib/graphql/schema/build_from_definition.rb +60 -18
  21. data/lib/graphql/schema/enum.rb +1 -0
  22. data/lib/graphql/schema/enum_value.rb +1 -0
  23. data/lib/graphql/schema/field.rb +44 -31
  24. data/lib/graphql/schema/field/dynamic_resolve.rb +4 -8
  25. data/lib/graphql/schema/input_object.rb +30 -19
  26. data/lib/graphql/schema/interface.rb +12 -5
  27. data/lib/graphql/schema/member.rb +10 -0
  28. data/lib/graphql/schema/member/build_type.rb +3 -1
  29. data/lib/graphql/schema/member/has_arguments.rb +50 -0
  30. data/lib/graphql/schema/member/has_fields.rb +1 -1
  31. data/lib/graphql/schema/member/instrumentation.rb +4 -4
  32. data/lib/graphql/schema/mutation.rb +195 -0
  33. data/lib/graphql/schema/object.rb +4 -5
  34. data/lib/graphql/schema/relay_classic_mutation.rb +85 -0
  35. data/lib/graphql/schema/scalar.rb +1 -0
  36. data/lib/graphql/schema/traversal.rb +1 -1
  37. data/lib/graphql/schema/union.rb +1 -0
  38. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  39. data/lib/graphql/unresolved_type_error.rb +3 -2
  40. data/lib/graphql/upgrader/member.rb +194 -19
  41. data/lib/graphql/version.rb +1 -1
  42. data/spec/fixtures/upgrader/delete_project.original.rb +28 -0
  43. data/spec/fixtures/upgrader/delete_project.transformed.rb +27 -0
  44. data/spec/fixtures/upgrader/increment_count.original.rb +59 -0
  45. data/spec/fixtures/upgrader/increment_count.transformed.rb +50 -0
  46. data/spec/graphql/execution/multiplex_spec.rb +1 -1
  47. data/spec/graphql/language/parser_spec.rb +0 -74
  48. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +2 -8
  49. data/spec/graphql/query_spec.rb +26 -0
  50. data/spec/graphql/relay/mutation_spec.rb +2 -2
  51. data/spec/graphql/schema/build_from_definition_spec.rb +59 -0
  52. data/spec/graphql/schema/field_spec.rb +24 -0
  53. data/spec/graphql/schema/input_object_spec.rb +1 -0
  54. data/spec/graphql/schema/interface_spec.rb +4 -1
  55. data/spec/graphql/schema/mutation_spec.rb +99 -0
  56. data/spec/graphql/schema/relay_classic_mutation_spec.rb +28 -0
  57. data/spec/support/jazz.rb +25 -1
  58. data/spec/support/star_wars/schema.rb +17 -27
  59. metadata +17 -2
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ # Mutations that extend this base class get some conventions added for free:
6
+ #
7
+ # - An argument called `clientMutationId` is _always_ added, but it's not passed
8
+ # to the resolve method. The value is re-inserted to the response. (It's for
9
+ # client libraries to manage optimistic updates.)
10
+ # - The returned object type always has a field called `clientMutationId` to support that.
11
+ # - The mutation accepts one argument called `input`, `argument`s defined in the mutation
12
+ # class are added to that input object, which is generated by the mutation.
13
+ #
14
+ # These conventions were first specified by Relay Classic, but they come in handy:
15
+ #
16
+ # - `clientMutationId` supports optimistic updates and cache rollbacks on the client
17
+ # - using a single `input:` argument makes it easy to post whole JSON objects to the mutation
18
+ # using one GraphQL variable (`$input`) instead of making a separate variable for each argument.
19
+ #
20
+ # @see {GraphQL::Schema::Mutation} for an example, it's basically the same.
21
+ #
22
+ class RelayClassicMutation < GraphQL::Schema::Mutation
23
+ # The payload should always include this field
24
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true)
25
+
26
+ class << self
27
+ # The base class for generated input object types
28
+ # @param new_class [Class] The base class to use for generating input object definitions
29
+ # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
30
+ def input_object_class(new_class = nil)
31
+ if new_class
32
+ @input_object_class = new_class
33
+ end
34
+ @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
35
+ end
36
+
37
+ # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
38
+ # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
39
+ def input_type(new_input_type = nil)
40
+ if new_input_type
41
+ @input_type = new_input_type
42
+ end
43
+ @input_type ||= generate_input_type
44
+ end
45
+
46
+ private
47
+
48
+ # Extend {Schema::Mutation.generate_field} to add the `input` argument
49
+ def generate_field
50
+ field_instance = super
51
+ field_instance.own_arguments.clear
52
+ field_instance.argument(:input, input_type, required: true)
53
+ field_instance
54
+ end
55
+
56
+ # Generate the input type for the `input:` argument
57
+ # To customize how input objects are generated, override this method
58
+ # @return [Class] a subclass of {.input_object_class}
59
+ def generate_input_type
60
+ mutation_args = arguments
61
+ mutation_name = graphql_name
62
+ mutation_class = self
63
+ Class.new(input_object_class) do
64
+ graphql_name("#{mutation_name}Input")
65
+ description("Autogenerated input type of #{mutation_name}")
66
+ mutation(mutation_class)
67
+ own_arguments.merge!(mutation_args)
68
+ argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
69
+ end
70
+ end
71
+
72
+ # Override {GraphQL::Schema::Mutation.resolve_field} to
73
+ # delete `client_mutation_id` from the kwargs.
74
+ def resolve_field(obj, args, ctx)
75
+ mutation = self.new(object: obj, arguments: args, context: ctx.query.context)
76
+ kwargs = args.to_kwargs
77
+ # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
78
+ kwargs.delete(:client_mutation_id)
79
+ extras.each { |e| kwargs[e] = ctx.public_send(e) }
80
+ mutation.resolve(**kwargs)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -19,6 +19,7 @@ module GraphQL
19
19
  type_defn.description = description
20
20
  type_defn.coerce_result = method(:coerce_result)
21
21
  type_defn.coerce_input = method(:coerce_input)
22
+ type_defn.metadata[:type_class] = self
22
23
  type_defn
23
24
  end
24
25
  end
@@ -158,7 +158,7 @@ Some late-bound types couldn't be resolved:
158
158
  end
159
159
  elsif !prev_type.equal?(type_defn)
160
160
  # If the previous entry in the map isn't the same object we just found, raise.
161
- raise("Duplicate type definition found for name '#{type_defn.name}' (#{prev_type.metadata[:object_class]}, #{type_defn.metadata[:object_class]}})")
161
+ raise("Duplicate type definition found for name '#{type_defn.name}' (#{prev_type.metadata[:type_class]}, #{type_defn.metadata[:type_class]}})")
162
162
  end
163
163
  when Class
164
164
  if member.respond_to?(:graphql_definition)
@@ -23,6 +23,7 @@ module GraphQL
23
23
  if respond_to?(:resolve_type)
24
24
  type_defn.resolve_type = method(:resolve_type)
25
25
  end
26
+ type_defn.metadata[:type_class] = self
26
27
  type_defn
27
28
  end
28
29
  end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  # })
40
40
  #
41
41
  # payload = {
42
- # result: result.subscription? ? nil : result.to_h,
42
+ # result: result.subscription? ? {data: nil} : result.to_h,
43
43
  # more: result.subscription?,
44
44
  # }
45
45
  #
@@ -26,8 +26,9 @@ module GraphQL
26
26
  @possible_types = possible_types
27
27
  message = "The value from \"#{field.name}\" on \"#{parent_type}\" could not be resolved to \"#{field.type}\". " \
28
28
  "(Received: `#{resolved_type.inspect}`, Expected: [#{possible_types.map(&:inspect).join(", ")}]) " \
29
- "Make sure you have defined a `type_from_object` proc on your schema and that value `#{value.inspect}` " \
30
- "gets resolved to a valid type."
29
+ "Make sure you have defined a `resolve_type` proc on your schema and that value `#{value.inspect}` " \
30
+ "gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an " \
31
+ "interface but isn't a return type of any other field."
31
32
  super(message)
32
33
  end
33
34
  end
@@ -107,6 +107,19 @@ module GraphQL
107
107
  end
108
108
  end
109
109
 
110
+ # Turns `{X} = GraphQL::Relay::Mutation.define do` into `class {X} < Mutations::BaseMutation`
111
+ class MutationDefineToClassTransform < Transform
112
+ # @param base_class_name [String] Replacement pattern for the base class name. Use this if your Mutation base class has a nonstandard name.
113
+ def initialize(base_class_name: "Mutations::BaseMutation")
114
+ @find_pattern = /([a-zA-Z_0-9:]*) = GraphQL::Relay::Mutation.define do/
115
+ @replace_pattern = "class \\1 < #{base_class_name}"
116
+ end
117
+
118
+ def apply(input_text)
119
+ input_text.sub(@find_pattern, @replace_pattern)
120
+ end
121
+ end
122
+
110
123
  # Remove `name "Something"` if it is redundant with the class name.
111
124
  # Or, if it is not redundant, move it to `graphql_name "Something"`.
112
125
  class NameTransform < Transform
@@ -138,7 +151,7 @@ module GraphQL
138
151
  keep_looking = true
139
152
  while keep_looking do
140
153
  keep_looking = false
141
- input_text = input_text.gsub(/(?<field>(?:field|connection|argument).*?,)\n(\s*)(?<next_line>.*)/) do
154
+ input_text = input_text.gsub(/(?<field>(?:field|input_field|return_field|connection|argument)(?:\(.*|.*,))\n\s*(?<next_line>.+)/) do
142
155
  keep_looking = true
143
156
  field = $~[:field].chomp
144
157
  next_line = $~[:next_line]
@@ -150,11 +163,21 @@ module GraphQL
150
163
  end
151
164
  end
152
165
 
166
+ # Remove parens from method call - normalize for processing
167
+ class RemoveMethodParensTransform < Transform
168
+ def apply(input_text)
169
+ input_text.sub(
170
+ /(field|input_field|return_field|connection|argument)\( *(.*?) *\) */,
171
+ '\1 \2'
172
+ )
173
+ end
174
+ end
175
+
153
176
  # Move `type X` to be the second positional argument to `field ...`
154
177
  class PositionalTypeArgTransform < Transform
155
178
  def apply(input_text)
156
179
  input_text.gsub(
157
- /(?<field>(?:field|connection|argument) :(?:[a-zA-Z_0-9]*)) do(?<block_contents>.*?)[ ]*type (?<return_type>.*?)\n/m
180
+ /(?<field>(?:field|input_field|return_field|connection|argument) :(?:[a-zA-Z_0-9]*)) do(?<block_contents>.*?)[ ]*type (?<return_type>.*?)\n/m
158
181
  ) do
159
182
  field = $~[:field]
160
183
  block_contents = $~[:block_contents]
@@ -183,7 +206,7 @@ module GraphQL
183
206
 
184
207
  def apply(input_text)
185
208
  input_text.gsub(
186
- /(?<field>(?:field|connection|argument).*) do(?<block_contents>.*?)[ ]*#{@kwarg} (?<kwarg_value>.*?)\n/m
209
+ /(?<field>(?:field|return_field|input_field|connection|argument).*) do(?<block_contents>.*?)[ ]*#{@kwarg} (?<kwarg_value>.*?)\n/m
187
210
  ) do
188
211
  field = $~[:field]
189
212
  block_contents = $~[:block_contents]
@@ -207,7 +230,7 @@ module GraphQL
207
230
  class RemoveRedundantKwargTransform < Transform
208
231
  def initialize(kwarg:)
209
232
  @kwarg = kwarg
210
- @finder_pattern = /(field|connection|argument) :(?<name>[a-zA-Z_0-9]*).*#{@kwarg}: ['":](?<kwarg_value>[a-zA-Z_0-9?!]+)['"]?/
233
+ @finder_pattern = /(field|return_field|input_field|connection|argument) :(?<name>[a-zA-Z_0-9]*).*#{@kwarg}: ['":](?<kwarg_value>[a-zA-Z_0-9?!]+)['"]?/
211
234
  end
212
235
 
213
236
  def apply(input_text)
@@ -227,7 +250,7 @@ module GraphQL
227
250
  # (They'll be automatically camelized later.)
228
251
  class UnderscoreizeFieldNameTransform < Transform
229
252
  def apply(input_text)
230
- input_text.gsub /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)/ do
253
+ input_text.gsub /(?<field_type>input_field|return_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)/ do
231
254
  field_type = $~[:field_type]
232
255
  camelized_name = $~[:name]
233
256
  underscored_name = underscorize(camelized_name)
@@ -304,6 +327,151 @@ module GraphQL
304
327
  end
305
328
  end
306
329
 
330
+ class MutationResolveProcToMethodTransform < Transform
331
+ # @param proc_name [String] The name of the proc to be moved to `def self.#{proc_name}`
332
+ def initialize(proc_name: "resolve")
333
+ @proc_name = proc_name
334
+ end
335
+
336
+ # TODO dedup with ResolveProcToMethodTransform
337
+ def apply(input_text)
338
+ if input_text =~ /GraphQL::Relay::Mutation\.define/
339
+ named_proc_processor = apply_processor(input_text, ProcToClassMethodTransform::NamedProcProcessor.new(@proc_name))
340
+ resolve_proc_processor = apply_processor(input_text, ResolveProcToMethodTransform::ResolveProcProcessor.new)
341
+ proc_body = input_text[named_proc_processor.proc_body_start..named_proc_processor.proc_body_end]
342
+ method_defn_indent = " " * named_proc_processor.proc_defn_indent
343
+
344
+ obj_arg_name, args_arg_name, ctx_arg_name = resolve_proc_processor.proc_arg_names
345
+ # This is not good, it will hit false positives
346
+ # Should use AST to make this substitution
347
+ if obj_arg_name != "_"
348
+ proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1@object\2')
349
+ end
350
+ if ctx_arg_name != "_"
351
+ proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1@context\2')
352
+ end
353
+
354
+ method_defn = "def #{@proc_name}(**#{args_arg_name})\n#{method_defn_indent} #{proc_body}\n#{method_defn_indent}end\n"
355
+ method_defn = trim_lines(method_defn)
356
+ # Update usage of args keys
357
+ method_defn = method_defn.gsub(/#{args_arg_name}(?<method_begin>\.key\?\(?|\[)["':](?<arg_name>[a-zA-Z0-9_]+)["']?(?<method_end>\]|\))?/) do
358
+ method_begin = $~[:method_begin]
359
+ arg_name = underscorize($~[:arg_name])
360
+ method_end = $~[:method_end]
361
+ "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}"
362
+ end
363
+ # replace the proc with the new method
364
+ input_text[named_proc_processor.proc_defn_start..named_proc_processor.proc_defn_end] = method_defn
365
+ end
366
+ input_text
367
+ end
368
+ end
369
+
370
+ # Find hash literals which are returned from mutation resolves,
371
+ # and convert their keys to underscores. This catches a lot of cases but misses
372
+ # hashes which are initialized anywhere except in the return expression.
373
+ class UnderscorizeMutationHashTransform < Transform
374
+ def apply(input_text)
375
+ if input_text =~ /def resolve\(\*\*/
376
+ processor = apply_processor(input_text, ReturnedHashLiteralProcessor.new)
377
+ # Use reverse_each to avoid messing up positions
378
+ processor.keys_to_upgrade.reverse_each do |key_data|
379
+ underscored_key = underscorize(key_data[:key].to_s)
380
+ if key_data[:operator] == ":"
381
+ input_text[key_data[:start]...key_data[:end]] = underscored_key
382
+ else
383
+ input_text[key_data[:start]...key_data[:end]] = ":#{underscored_key}"
384
+ end
385
+ end
386
+ end
387
+ input_text
388
+ end
389
+
390
+ class ReturnedHashLiteralProcessor < Parser::AST::Processor
391
+ attr_reader :keys_to_upgrade
392
+ def initialize
393
+ @keys_to_upgrade = []
394
+ end
395
+
396
+ def on_def(node)
397
+ method_name, _args, body = *node
398
+ if method_name == :resolve
399
+ possible_returned_hashes = find_returned_hashes(body, returning: false)
400
+ possible_returned_hashes.each do |hash_node|
401
+ pairs = *hash_node
402
+ pairs.each do |pair_node|
403
+ if pair_node.type == :pair # Skip over :kwsplat
404
+ pair_k, _pair_v = *pair_node
405
+ if pair_k.type == :sym && pair_k.children[0].to_s =~ /[a-z][A-Z]/ # Does it have any camelcase boundaries?
406
+ source_exp = pair_k.loc.expression
407
+ @keys_to_upgrade << {
408
+ start: source_exp.begin.begin_pos,
409
+ end: source_exp.end.end_pos,
410
+ key: pair_k.children[0],
411
+ operator: pair_node.loc.operator.source,
412
+ }
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end
418
+
419
+ end
420
+
421
+ private
422
+
423
+ # Look for hash nodes, starting from `node`.
424
+ # Return hash nodes that are valid candiates for returning from this method.
425
+ def find_returned_hashes(node, returning:)
426
+ if node.is_a?(Array)
427
+ *possible_returns, last_expression = *node
428
+ return possible_returns.map { |c| find_returned_hashes(c, returning: false) }.flatten +
429
+ # Check the last expression of a method body
430
+ find_returned_hashes(last_expression, returning: returning)
431
+ end
432
+
433
+ case node.type
434
+ when :hash
435
+ if returning
436
+ [node]
437
+ else
438
+ # This is some random hash literal
439
+ []
440
+ end
441
+ when :begin
442
+ # Check the last expression of a method body
443
+ find_returned_hashes(node.children, returning: true)
444
+ when :resbody
445
+ _condition, _assign, body = *node
446
+ find_returned_hashes(body, returning: returning)
447
+ when :kwbegin
448
+ find_returned_hashes(node.children, returning: returning)
449
+ when :rescue
450
+ try_body, rescue_body, _ensure_body = *node
451
+ find_returned_hashes(try_body, returning: returning) + find_returned_hashes(rescue_body, returning: returning)
452
+ when :block
453
+ # Check methods with blocks for possible returns
454
+ method_call, _args, *body = *node
455
+ if method_call.type == :send
456
+ find_returned_hashes(body, returning: returning)
457
+ end
458
+ when :if
459
+ # Check each branch of a conditional
460
+ _condition, *branches = *node
461
+ branches.compact.map { |b| find_returned_hashes(b, returning: returning) }.flatten
462
+ when :return
463
+ find_returned_hashes(node.children.first, returning: true)
464
+ else
465
+ []
466
+ end
467
+ rescue
468
+ p "--- UnderscorizeMutationHashTransform crashed on node: ---"
469
+ p node
470
+ raise
471
+ end
472
+
473
+ end
474
+ end
307
475
 
308
476
  class ResolveProcToMethodTransform < Transform
309
477
  def apply(input_text)
@@ -334,10 +502,10 @@ module GraphQL
334
502
  # This is not good, it will hit false positives
335
503
  # Should use AST to make this substitution
336
504
  if obj_arg_name != "_"
337
- proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w]|$)/, '\1@object\2')
505
+ proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1@object\2')
338
506
  end
339
507
  if ctx_arg_name != "_"
340
- proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w]|$)/, '\1@context\2')
508
+ proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1@context\2')
341
509
  end
342
510
 
343
511
  method_def_indent = " " * (processor.resolve_indent - 2)
@@ -452,8 +620,8 @@ module GraphQL
452
620
 
453
621
  class UpdateMethodSignatureTransform < Transform
454
622
  def apply(input_text)
455
- input_text.scan(/(?:input_field|field|connection|argument) .*$/).each do |field|
456
- matches = /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)?(:?, +(?<return_type>([A-Za-z\[\]\.\!_0-9\(\)]|::|-> ?\{ ?| ?\})+))?(?<remainder>( |,|$).*)/.match(field)
623
+ input_text.scan(/(?:input_field|field|return_field|connection|argument) .*$/).each do |field|
624
+ matches = /(?<field_type>input_field|return_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)?(:?, +(?<return_type>([A-Za-z\[\]\.\!_0-9\(\)]|::|-> ?\{ ?| ?\})+))?(?<remainder>( |,|$).*)/.match(field)
457
625
  if matches
458
626
  name = matches[:name]
459
627
  return_type = matches[:return_type]
@@ -543,7 +711,8 @@ module GraphQL
543
711
  end
544
712
 
545
713
  def apply(input_text)
546
- if input_text =~ @interface_file_pattern && input_text =~ /\n *def /
714
+ # See if it its an interface file with any instance methods
715
+ if input_text =~ @interface_file_pattern && input_text =~ /\n *def (?!(self|[A-Z]))/
547
716
  # Extract the method bodies and figure out where the module should be inserted
548
717
  method_bodies = []
549
718
  processor = apply_processor(input_text, InterfaceMethodProcessor.new)
@@ -632,6 +801,9 @@ module GraphQL
632
801
 
633
802
  DEFAULT_TYPE_TRANSFORMS = [
634
803
  TypeDefineToClassTransform,
804
+ MutationResolveProcToMethodTransform, # Do this before switching to class, so we can detect that its a mutation
805
+ UnderscorizeMutationHashTransform,
806
+ MutationDefineToClassTransform,
635
807
  NameTransform,
636
808
  InterfacesToImplementsTransform,
637
809
  PossibleTypesTransform,
@@ -642,6 +814,7 @@ module GraphQL
642
814
 
643
815
  DEFAULT_FIELD_TRANSFORMS = [
644
816
  RemoveNewlinesTransform,
817
+ RemoveMethodParensTransform,
645
818
  PositionalTypeArgTransform,
646
819
  ConfigurationToKwargTransform.new(kwarg: "property"),
647
820
  ConfigurationToKwargTransform.new(kwarg: "description"),
@@ -719,9 +892,11 @@ module GraphQL
719
892
  # For each of the locations we found, extract the text for that definition.
720
893
  # The text will be transformed independently,
721
894
  # then the transformed text will replace the original text.
722
- finder.locations.each do |name, (starting_idx, ending_idx)|
723
- field_source = type_source[starting_idx..ending_idx]
724
- field_sources << field_source
895
+ finder.locations.each do |category, locs|
896
+ locs.each do |name, (starting_idx, ending_idx)|
897
+ field_source = type_source[starting_idx..ending_idx]
898
+ field_sources << field_source
899
+ end
725
900
  end
726
901
  # Here's a crazy thing: the transformation is pure,
727
902
  # so definitions like `argument :id, types.ID` can be transformed once
@@ -739,13 +914,13 @@ module GraphQL
739
914
  class FieldFinder < Parser::AST::Processor
740
915
  # These methods are definition DSLs which may accept a block,
741
916
  # each of these definitions is passed for transformation in its own right
742
- DEFINITION_METHODS = [:field, :connection, :input_field, :argument]
917
+ DEFINITION_METHODS = [:field, :connection, :input_field, :return_field, :argument]
743
918
  attr_reader :locations
744
919
 
745
920
  def initialize
746
- # Pairs of `{ name => [start, end] }`,
747
- # since we know fields are unique by name.
748
- @locations = {}
921
+ # Pairs of `{ { method_name => { name => [start, end] } }`,
922
+ # since fields/arguments are unique by name, within their category
923
+ @locations = Hash.new { |h,k| h[k] = {} }
749
924
  end
750
925
 
751
926
  # @param send_node [node] The node which might be a `field` call, etc
@@ -757,10 +932,10 @@ module GraphQL
757
932
  name = arg_nodes[0]
758
933
  # This field may have already been added because
759
934
  # we find `(block ...)` nodes _before_ we find `(send ...)` nodes.
760
- if @locations[name].nil?
935
+ if @locations[method_name][name].nil?
761
936
  starting_idx = source_node.loc.expression.begin.begin_pos
762
937
  ending_idx = source_node.loc.expression.end.end_pos
763
- @locations[name] = [starting_idx, ending_idx]
938
+ @locations[method_name][name] = [starting_idx, ending_idx]
764
939
  end
765
940
  end
766
941
  end