graphql 1.8.0.pre9 → 1.8.0.pre10

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 (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