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.
- checksums.yaml +4 -4
- data/lib/graphql/argument.rb +1 -0
- data/lib/graphql/base_type.rb +2 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +110 -0
- data/lib/graphql/deprecated_dsl.rb +15 -3
- data/lib/graphql/directive.rb +1 -0
- data/lib/graphql/enum_type.rb +2 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/field.rb +2 -0
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/schema_field.rb +1 -1
- data/lib/graphql/introspection/type_by_name_field.rb +1 -1
- data/lib/graphql/language/parser.rb +25 -25
- data/lib/graphql/language/parser.y +7 -7
- data/lib/graphql/query/arguments.rb +12 -0
- data/lib/graphql/relay/mutation/instrumentation.rb +1 -1
- data/lib/graphql/relay/mutation/resolve.rb +5 -1
- data/lib/graphql/schema.rb +5 -1
- data/lib/graphql/schema/argument.rb +1 -0
- data/lib/graphql/schema/build_from_definition.rb +60 -18
- data/lib/graphql/schema/enum.rb +1 -0
- data/lib/graphql/schema/enum_value.rb +1 -0
- data/lib/graphql/schema/field.rb +44 -31
- data/lib/graphql/schema/field/dynamic_resolve.rb +4 -8
- data/lib/graphql/schema/input_object.rb +30 -19
- data/lib/graphql/schema/interface.rb +12 -5
- data/lib/graphql/schema/member.rb +10 -0
- data/lib/graphql/schema/member/build_type.rb +3 -1
- data/lib/graphql/schema/member/has_arguments.rb +50 -0
- data/lib/graphql/schema/member/has_fields.rb +1 -1
- data/lib/graphql/schema/member/instrumentation.rb +4 -4
- data/lib/graphql/schema/mutation.rb +195 -0
- data/lib/graphql/schema/object.rb +4 -5
- data/lib/graphql/schema/relay_classic_mutation.rb +85 -0
- data/lib/graphql/schema/scalar.rb +1 -0
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/union.rb +1 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/unresolved_type_error.rb +3 -2
- data/lib/graphql/upgrader/member.rb +194 -19
- data/lib/graphql/version.rb +1 -1
- data/spec/fixtures/upgrader/delete_project.original.rb +28 -0
- data/spec/fixtures/upgrader/delete_project.transformed.rb +27 -0
- data/spec/fixtures/upgrader/increment_count.original.rb +59 -0
- data/spec/fixtures/upgrader/increment_count.transformed.rb +50 -0
- data/spec/graphql/execution/multiplex_spec.rb +1 -1
- data/spec/graphql/language/parser_spec.rb +0 -74
- data/spec/graphql/query/serial_execution/value_resolution_spec.rb +2 -8
- data/spec/graphql/query_spec.rb +26 -0
- data/spec/graphql/relay/mutation_spec.rb +2 -2
- data/spec/graphql/schema/build_from_definition_spec.rb +59 -0
- data/spec/graphql/schema/field_spec.rb +24 -0
- data/spec/graphql/schema/input_object_spec.rb +1 -0
- data/spec/graphql/schema/interface_spec.rb +4 -1
- data/spec/graphql/schema/mutation_spec.rb +99 -0
- data/spec/graphql/schema/relay_classic_mutation_spec.rb +28 -0
- data/spec/support/jazz.rb +25 -1
- data/spec/support/star_wars/schema.rb +17 -27
- 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
|
@@ -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[:
|
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)
|
data/lib/graphql/schema/union.rb
CHANGED
@@ -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 `
|
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)
|
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
|
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 |
|
723
|
-
|
724
|
-
|
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
|
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
|