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