graphql 2.4.11 → 2.4.15

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +37 -40
  4. data/lib/graphql/analysis.rb +12 -9
  5. data/lib/graphql/backtrace/table.rb +37 -14
  6. data/lib/graphql/execution/interpreter/runtime.rb +12 -2
  7. data/lib/graphql/execution/interpreter.rb +1 -0
  8. data/lib/graphql/invalid_null_error.rb +1 -5
  9. data/lib/graphql/language/lexer.rb +7 -3
  10. data/lib/graphql/language/nodes.rb +3 -0
  11. data/lib/graphql/language/parser.rb +1 -1
  12. data/lib/graphql/language/static_visitor.rb +37 -33
  13. data/lib/graphql/language/visitor.rb +59 -55
  14. data/lib/graphql/schema/argument.rb +7 -8
  15. data/lib/graphql/schema/build_from_definition.rb +99 -53
  16. data/lib/graphql/schema/directive.rb +1 -1
  17. data/lib/graphql/schema/enum.rb +2 -2
  18. data/lib/graphql/schema/enum_value.rb +1 -1
  19. data/lib/graphql/schema/field.rb +2 -2
  20. data/lib/graphql/schema/input_object.rb +16 -16
  21. data/lib/graphql/schema/interface.rb +1 -1
  22. data/lib/graphql/schema/member/has_directives.rb +1 -1
  23. data/lib/graphql/schema/member/has_fields.rb +1 -1
  24. data/lib/graphql/schema/member/has_interfaces.rb +1 -1
  25. data/lib/graphql/schema/member/scoped.rb +1 -1
  26. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  27. data/lib/graphql/schema/resolver.rb +5 -1
  28. data/lib/graphql/schema.rb +6 -4
  29. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  30. data/lib/graphql/testing/helpers.rb +5 -2
  31. data/lib/graphql/tracing/appoptics_trace.rb +4 -0
  32. data/lib/graphql/tracing/appsignal_trace.rb +4 -0
  33. data/lib/graphql/tracing/data_dog_trace.rb +4 -0
  34. data/lib/graphql/tracing/new_relic_trace.rb +41 -24
  35. data/lib/graphql/tracing/notifications_trace.rb +4 -0
  36. data/lib/graphql/tracing/platform_trace.rb +5 -0
  37. data/lib/graphql/tracing/prometheus_trace.rb +4 -0
  38. data/lib/graphql/tracing/scout_trace.rb +3 -0
  39. data/lib/graphql/tracing/sentry_trace.rb +4 -0
  40. data/lib/graphql/tracing/statsd_trace.rb +4 -0
  41. data/lib/graphql/types/relay/connection_behaviors.rb +1 -1
  42. data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
  43. data/lib/graphql/version.rb +1 -1
  44. metadata +3 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba5296cae016dea52b006bebe1515d6eef54876ec9f6437f5414bfccd7d8cf40
4
- data.tar.gz: 75bf06fbb277d4e9c400f57998e0de5d2a6e4a41d562c71b0969fc30fe2bbb64
3
+ metadata.gz: aa77120ec7a120069d426d9bb854f2eb2d20800da8cd1ff80e96842f97799a68
4
+ data.tar.gz: a104d683b69d1237172b1321006976832ed71cb52d8ec983600ce46e9dfbf343
5
5
  SHA512:
6
- metadata.gz: e05120bb3377cbda64651ef2128b97daedf1b58e5e4a36b90d01d8ba4435a26e0dce6e270dfbc22ae75d29abcbe4d6f50c853ad6e13225a71eafeadbaad05519
7
- data.tar.gz: 2caa24da0288b0efab47b94a441351f8bbd0d1d3cb8bac1c834102fedb2ea350787fb74e2fe1d502971b181308f2b17b1e5b5273a2bf2a868d4917b29fc27f9b
6
+ metadata.gz: e71128b43511f0e266f5cf4be57d0ae3527ce42d616b47fcbd4756a0dc6b9b939ab470808603a68e136bf6501037717bd2148c97fa50c6ee65e527d63c46f252
7
+ data.tar.gz: 601a2d75581270386e8e8b25038210c6a04aa3c81a433e72fb1e46a285b7e6651e0935a1017f04f148c43665d4407f0384e058bdef902b7a391fa70686a009be
@@ -42,6 +42,7 @@ module GraphQL
42
42
  raise GraphQL::RequiredImplementationMissingError
43
43
  end
44
44
 
45
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
45
46
  class << self
46
47
  private
47
48
 
@@ -72,7 +73,7 @@ module GraphQL
72
73
  build_visitor_hooks :variable_definition
73
74
  build_visitor_hooks :variable_identifier
74
75
  build_visitor_hooks :abstract_node
75
-
76
+ # rubocop:enable Development/NoEvalCop
76
77
  protected
77
78
 
78
79
  # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
@@ -10,7 +10,7 @@ module GraphQL
10
10
  #
11
11
  # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
12
12
  class Visitor < GraphQL::Language::StaticVisitor
13
- def initialize(query:, analyzers:)
13
+ def initialize(query:, analyzers:, timeout:)
14
14
  @analyzers = analyzers
15
15
  @path = []
16
16
  @object_types = []
@@ -24,6 +24,11 @@ module GraphQL
24
24
  @types = query.types
25
25
  @response_path = []
26
26
  @skip_stack = [false]
27
+ @timeout_time = if timeout
28
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) + timeout
29
+ else
30
+ Float::INFINITY
31
+ end
27
32
  super(query.selected_operation)
28
33
  end
29
34
 
@@ -64,6 +69,7 @@ module GraphQL
64
69
  @response_path.dup
65
70
  end
66
71
 
72
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
67
73
  # Visitor Hooks
68
74
  [
69
75
  :operation_definition, :fragment_definition,
@@ -72,28 +78,26 @@ module GraphQL
72
78
  module_eval <<-RUBY, __FILE__, __LINE__
73
79
  def call_on_enter_#{node_type}(node, parent)
74
80
  @analyzers.each do |a|
75
- begin
76
- a.on_enter_#{node_type}(node, parent, self)
77
- rescue AnalysisError => err
78
- @rescued_errors << err
79
- end
81
+ a.on_enter_#{node_type}(node, parent, self)
82
+ rescue AnalysisError => err
83
+ @rescued_errors << err
80
84
  end
81
85
  end
82
86
 
83
87
  def call_on_leave_#{node_type}(node, parent)
84
88
  @analyzers.each do |a|
85
- begin
86
- a.on_leave_#{node_type}(node, parent, self)
87
- rescue AnalysisError => err
88
- @rescued_errors << err
89
- end
89
+ a.on_leave_#{node_type}(node, parent, self)
90
+ rescue AnalysisError => err
91
+ @rescued_errors << err
90
92
  end
91
93
  end
92
94
 
93
95
  RUBY
94
96
  end
97
+ # rubocop:enable Development/NoEvalCop
95
98
 
96
99
  def on_operation_definition(node, parent)
100
+ check_timeout
97
101
  object_type = @schema.root_type_for_operation(node.operation_type)
98
102
  @object_types.push(object_type)
99
103
  @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
@@ -104,31 +108,27 @@ module GraphQL
104
108
  @path.pop
105
109
  end
106
110
 
107
- def on_fragment_definition(node, parent)
108
- on_fragment_with_type(node) do
109
- @path.push("fragment #{node.name}")
110
- @in_fragment_def = false
111
- call_on_enter_fragment_definition(node, parent)
112
- super
113
- @in_fragment_def = false
114
- call_on_leave_fragment_definition(node, parent)
115
- end
116
- end
117
-
118
111
  def on_inline_fragment(node, parent)
119
- on_fragment_with_type(node) do
120
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121
- @skipping = @skip_stack.last || skip?(node)
122
- @skip_stack << @skipping
123
-
124
- call_on_enter_inline_fragment(node, parent)
125
- super
126
- @skipping = @skip_stack.pop
127
- call_on_leave_inline_fragment(node, parent)
112
+ check_timeout
113
+ object_type = if node.type
114
+ @types.type(node.type.name)
115
+ else
116
+ @object_types.last
128
117
  end
118
+ @object_types.push(object_type)
119
+ @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
120
+ @skipping = @skip_stack.last || skip?(node)
121
+ @skip_stack << @skipping
122
+ call_on_enter_inline_fragment(node, parent)
123
+ super
124
+ @skipping = @skip_stack.pop
125
+ call_on_leave_inline_fragment(node, parent)
126
+ @object_types.pop
127
+ @path.pop
129
128
  end
130
129
 
131
130
  def on_field(node, parent)
131
+ check_timeout
132
132
  @response_path.push(node.alias || node.name)
133
133
  parent_type = @object_types.last
134
134
  # This could be nil if the previous field wasn't found:
@@ -156,6 +156,7 @@ module GraphQL
156
156
  end
157
157
 
158
158
  def on_directive(node, parent)
159
+ check_timeout
159
160
  directive_defn = @schema.directives[node.name]
160
161
  @directive_definitions.push(directive_defn)
161
162
  call_on_enter_directive(node, parent)
@@ -165,6 +166,7 @@ module GraphQL
165
166
  end
166
167
 
167
168
  def on_argument(node, parent)
169
+ check_timeout
168
170
  argument_defn = if (arg = @argument_definitions.last)
169
171
  arg_type = arg.type.unwrap
170
172
  if arg_type.kind.input_object?
@@ -190,6 +192,7 @@ module GraphQL
190
192
  end
191
193
 
192
194
  def on_fragment_spread(node, parent)
195
+ check_timeout
193
196
  @path.push("... #{node.name}")
194
197
  @skipping = @skip_stack.last || skip?(node)
195
198
  @skip_stack << @skipping
@@ -267,16 +270,10 @@ module GraphQL
267
270
  !dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
268
271
  end
269
272
 
270
- def on_fragment_with_type(node)
271
- object_type = if node.type
272
- @types.type(node.type.name)
273
- else
274
- @object_types.last
273
+ def check_timeout
274
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) > @timeout_time
275
+ raise GraphQL::Analysis::TimeoutError
275
276
  end
276
- @object_types.push(object_type)
277
- yield(node)
278
- @object_types.pop
279
- @path.pop
280
277
  end
281
278
  end
282
279
  end
@@ -6,11 +6,16 @@ require "graphql/analysis/query_complexity"
6
6
  require "graphql/analysis/max_query_complexity"
7
7
  require "graphql/analysis/query_depth"
8
8
  require "graphql/analysis/max_query_depth"
9
- require "timeout"
10
-
11
9
  module GraphQL
12
10
  module Analysis
13
11
  AST = self
12
+
13
+ class TimeoutError < AnalysisError
14
+ def initialize(...)
15
+ super("Timeout on validation of query")
16
+ end
17
+ end
18
+
14
19
  module_function
15
20
  # Analyze a multiplex, and all queries within.
16
21
  # Multiplex analyzers are ran for all queries, keeping state.
@@ -61,13 +66,11 @@ module GraphQL
61
66
  if !analyzers_to_run.empty?
62
67
  visitor = GraphQL::Analysis::Visitor.new(
63
68
  query: query,
64
- analyzers: analyzers_to_run
69
+ analyzers: analyzers_to_run,
70
+ timeout: query.validate_timeout_remaining,
65
71
  )
66
72
 
67
- # `nil` or `0` causes no timeout
68
- Timeout::timeout(query.validate_timeout_remaining) do
69
- visitor.visit
70
- end
73
+ visitor.visit
71
74
 
72
75
  if !visitor.rescued_errors.empty?
73
76
  return visitor.rescued_errors
@@ -79,8 +82,8 @@ module GraphQL
79
82
  []
80
83
  end
81
84
  end
82
- rescue Timeout::Error
83
- [GraphQL::AnalysisError.new("Timeout on validation of query")]
85
+ rescue TimeoutError => err
86
+ [err]
84
87
  rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
85
88
  # This error was raised during analysis and will be returned the client before execution
86
89
  []
@@ -78,23 +78,32 @@ module GraphQL
78
78
  end
79
79
  end
80
80
 
81
-
82
81
  object = result.graphql_application_value.object.inspect
83
- ast_node = result.graphql_selections.find { |s| s.alias == last_part || s.name == last_part }
84
- field_defn = query.get_field(result.graphql_result_type, ast_node.name)
85
- args = query.arguments_for(ast_node, field_defn).to_h
86
- field_path = field_defn.path
87
- if ast_node.alias
88
- field_path += " as #{ast_node.alias}"
82
+ ast_node = nil
83
+ result.graphql_selections.each do |s|
84
+ found_ast_node = find_ast_node(s, last_part)
85
+ if found_ast_node
86
+ ast_node = found_ast_node
87
+ break
88
+ end
89
89
  end
90
90
 
91
- rows << [
92
- ast_node.position.join(":"),
93
- field_path,
94
- "#{object}",
95
- args.inspect,
96
- inspect_result(@override_value)
97
- ]
91
+ if ast_node
92
+ field_defn = query.get_field(result.graphql_result_type, ast_node.name)
93
+ args = query.arguments_for(ast_node, field_defn).to_h
94
+ field_path = field_defn.path
95
+ if ast_node.alias
96
+ field_path += " as #{ast_node.alias}"
97
+ end
98
+
99
+ rows << [
100
+ ast_node.position.join(":"),
101
+ field_path,
102
+ "#{object}",
103
+ args.inspect,
104
+ inspect_result(@override_value)
105
+ ]
106
+ end
98
107
 
99
108
  rows << HEADERS
100
109
  rows.reverse!
@@ -102,6 +111,20 @@ module GraphQL
102
111
  end
103
112
  end
104
113
 
114
+ def find_ast_node(node, last_part)
115
+ return nil unless node
116
+ return node if node.respond_to?(:alias) && node.respond_to?(:name) && (node.alias == last_part || node.name == last_part)
117
+ return nil unless node.respond_to?(:selections)
118
+ return nil if node.selections.nil? || node.selections.empty?
119
+
120
+ node.selections.each do |child|
121
+ child_ast_node = find_ast_node(child, last_part)
122
+ return child_ast_node if child_ast_node
123
+ end
124
+
125
+ nil
126
+ end
127
+
105
128
  # @return [String]
106
129
  def render_table(rows)
107
130
  max = Array.new(HEADERS.length, MIN_COL_WIDTH)
@@ -473,7 +473,7 @@ module GraphQL
473
473
  # When this comes from a list item, use the parent object:
474
474
  parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
475
475
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
476
- err = parent_type::InvalidNullError.new(parent_type, field, value, ast_node)
476
+ err = parent_type::InvalidNullError.new(parent_type, field, ast_node)
477
477
  schema.type_error(err, context)
478
478
  end
479
479
  else
@@ -587,7 +587,17 @@ module GraphQL
587
587
  set_result(selection_result, result_name, r, false, is_non_null)
588
588
  r
589
589
  when "UNION", "INTERFACE"
590
- resolved_type_or_lazy = resolve_type(current_type, value)
590
+ resolved_type_or_lazy = begin
591
+ resolve_type(current_type, value)
592
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
593
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
594
+ rescue StandardError => err
595
+ begin
596
+ query.handle_or_reraise(err)
597
+ rescue GraphQL::ExecutionError => ex_err
598
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
599
+ end
600
+ end
591
601
  after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
592
602
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
593
603
  resolved_type, resolved_value = resolved_type_result
@@ -33,6 +33,7 @@ module GraphQL
33
33
  end
34
34
  end
35
35
 
36
+ return GraphQL::EmptyObjects::EMPTY_ARRAY if queries.empty?
36
37
 
37
38
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
38
39
  Fiber[:__graphql_current_multiplex] = multiplex
@@ -9,16 +9,12 @@ module GraphQL
9
9
  # @return [GraphQL::Field] The field which failed to return a value
10
10
  attr_reader :field
11
11
 
12
- # @return [nil, GraphQL::ExecutionError] The invalid value for this field
13
- attr_reader :value
14
-
15
12
  # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
16
13
  attr_reader :ast_node
17
14
 
18
- def initialize(parent_type, field, value, ast_node)
15
+ def initialize(parent_type, field, ast_node)
19
16
  @parent_type = parent_type
20
17
  @field = field
21
- @value = value
22
18
  @ast_node = ast_node
23
19
  super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
24
20
  end
@@ -13,17 +13,21 @@ module GraphQL
13
13
  @pos = nil
14
14
  @max_tokens = max_tokens || Float::INFINITY
15
15
  @tokens_count = 0
16
+ @finished = false
16
17
  end
17
18
 
18
- def eos?
19
- @scanner.eos?
19
+ def finished?
20
+ @finished
20
21
  end
21
22
 
22
23
  attr_reader :pos, :tokens_count
23
24
 
24
25
  def advance
25
26
  @scanner.skip(IGNORE_REGEXP)
26
- return false if @scanner.eos?
27
+ if @scanner.eos?
28
+ @finished = true
29
+ return false
30
+ end
27
31
  @tokens_count += 1
28
32
  if @tokens_count > @max_tokens
29
33
  raise_parse_error("This query is too large to execute.")
@@ -141,6 +141,8 @@ module GraphQL
141
141
  end
142
142
 
143
143
  class << self
144
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
145
+
144
146
  # Add a default `#visit_method` and `#children_method_name` using the class name
145
147
  def inherited(child_class)
146
148
  super
@@ -343,6 +345,7 @@ module GraphQL
343
345
  RUBY
344
346
  end
345
347
  end
348
+ # rubocop:enable Development/NoEvalCop
346
349
  end
347
350
  end
348
351
 
@@ -110,7 +110,7 @@ module GraphQL
110
110
  # Only ignored characters is not a valid document
111
111
  raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @graphql_str)
112
112
  end
113
- while !@lexer.eos?
113
+ while !@lexer.finished?
114
114
  defns << definition
115
115
  end
116
116
  Document.new(pos: 0, definitions: defns, filename: @filename, source: self)
@@ -22,39 +22,6 @@ module GraphQL
22
22
  end
23
23
  end
24
24
 
25
- # We don't use `alias` here because it breaks `super`
26
- def self.make_visit_methods(ast_node_class)
27
- node_method = ast_node_class.visit_method
28
- children_of_type = ast_node_class.children_of_type
29
- child_visit_method = :"#{node_method}_children"
30
-
31
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
32
- # The default implementation for visiting an AST node.
33
- # It doesn't _do_ anything, but it continues to visiting the node's children.
34
- # To customize this hook, override one of its make_visit_methods (or the base method?)
35
- # in your subclasses.
36
- #
37
- # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
38
- # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
39
- # @return [void]
40
- def #{node_method}(node, parent)
41
- #{
42
- if method_defined?(child_visit_method)
43
- "#{child_visit_method}(node)"
44
- elsif children_of_type
45
- children_of_type.map do |child_accessor, child_class|
46
- "node.#{child_accessor}.each do |child_node|
47
- #{child_class.visit_method}(child_node, node)
48
- end"
49
- end.join("\n")
50
- else
51
- ""
52
- end
53
- }
54
- end
55
- RUBY
56
- end
57
-
58
25
  def on_document_children(document_node)
59
26
  document_node.children.each do |child_node|
60
27
  visit_method = child_node.visit_method
@@ -123,6 +90,41 @@ module GraphQL
123
90
  end
124
91
  end
125
92
 
93
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
94
+
95
+ # We don't use `alias` here because it breaks `super`
96
+ def self.make_visit_methods(ast_node_class)
97
+ node_method = ast_node_class.visit_method
98
+ children_of_type = ast_node_class.children_of_type
99
+ child_visit_method = :"#{node_method}_children"
100
+
101
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
102
+ # The default implementation for visiting an AST node.
103
+ # It doesn't _do_ anything, but it continues to visiting the node's children.
104
+ # To customize this hook, override one of its make_visit_methods (or the base method?)
105
+ # in your subclasses.
106
+ #
107
+ # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
108
+ # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
109
+ # @return [void]
110
+ def #{node_method}(node, parent)
111
+ #{
112
+ if method_defined?(child_visit_method)
113
+ "#{child_visit_method}(node)"
114
+ elsif children_of_type
115
+ children_of_type.map do |child_accessor, child_class|
116
+ "node.#{child_accessor}.each do |child_node|
117
+ #{child_class.visit_method}(child_node, node)
118
+ end"
119
+ end.join("\n")
120
+ else
121
+ ""
122
+ end
123
+ }
124
+ end
125
+ RUBY
126
+ end
127
+
126
128
  [
127
129
  Language::Nodes::Argument,
128
130
  Language::Nodes::Directive,
@@ -162,6 +164,8 @@ module GraphQL
162
164
  ].each do |ast_node_class|
163
165
  make_visit_methods(ast_node_class)
164
166
  end
167
+
168
+ # rubocop:disable Development/NoEvalCop
165
169
  end
166
170
  end
167
171
  end
@@ -61,61 +61,6 @@ module GraphQL
61
61
  end
62
62
  end
63
63
 
64
- # We don't use `alias` here because it breaks `super`
65
- def self.make_visit_methods(ast_node_class)
66
- node_method = ast_node_class.visit_method
67
- children_of_type = ast_node_class.children_of_type
68
- child_visit_method = :"#{node_method}_children"
69
-
70
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
71
- # The default implementation for visiting an AST node.
72
- # It doesn't _do_ anything, but it continues to visiting the node's children.
73
- # To customize this hook, override one of its make_visit_methods (or the base method?)
74
- # in your subclasses.
75
- #
76
- # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
77
- # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
78
- # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
79
- def #{node_method}(node, parent)
80
- if node.equal?(DELETE_NODE)
81
- # This might be passed to `super(DELETE_NODE, ...)`
82
- # by a user hook, don't want to keep visiting in that case.
83
- [node, parent]
84
- else
85
- new_node = node
86
- #{
87
- if method_defined?(child_visit_method)
88
- "new_node = #{child_visit_method}(new_node)"
89
- elsif children_of_type
90
- children_of_type.map do |child_accessor, child_class|
91
- "node.#{child_accessor}.each do |child_node|
92
- new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
93
- # Reassign `node` in case the child hook makes a modification
94
- if new_child_and_node.is_a?(Array)
95
- new_node = new_child_and_node[1]
96
- end
97
- end"
98
- end.join("\n")
99
- else
100
- ""
101
- end
102
- }
103
-
104
- if new_node.equal?(node)
105
- [node, parent]
106
- else
107
- [new_node, parent]
108
- end
109
- end
110
- end
111
-
112
- def #{node_method}_with_modifications(node, parent)
113
- new_node_and_new_parent = #{node_method}(node, parent)
114
- apply_modifications(node, parent, new_node_and_new_parent)
115
- end
116
- RUBY
117
- end
118
-
119
64
  def on_document_children(document_node)
120
65
  new_node = document_node
121
66
  document_node.children.each do |child_node|
@@ -216,6 +161,63 @@ module GraphQL
216
161
  new_node
217
162
  end
218
163
 
164
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
165
+
166
+ # We don't use `alias` here because it breaks `super`
167
+ def self.make_visit_methods(ast_node_class)
168
+ node_method = ast_node_class.visit_method
169
+ children_of_type = ast_node_class.children_of_type
170
+ child_visit_method = :"#{node_method}_children"
171
+
172
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
173
+ # The default implementation for visiting an AST node.
174
+ # It doesn't _do_ anything, but it continues to visiting the node's children.
175
+ # To customize this hook, override one of its make_visit_methods (or the base method?)
176
+ # in your subclasses.
177
+ #
178
+ # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
179
+ # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
180
+ # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
181
+ def #{node_method}(node, parent)
182
+ if node.equal?(DELETE_NODE)
183
+ # This might be passed to `super(DELETE_NODE, ...)`
184
+ # by a user hook, don't want to keep visiting in that case.
185
+ [node, parent]
186
+ else
187
+ new_node = node
188
+ #{
189
+ if method_defined?(child_visit_method)
190
+ "new_node = #{child_visit_method}(new_node)"
191
+ elsif children_of_type
192
+ children_of_type.map do |child_accessor, child_class|
193
+ "node.#{child_accessor}.each do |child_node|
194
+ new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
195
+ # Reassign `node` in case the child hook makes a modification
196
+ if new_child_and_node.is_a?(Array)
197
+ new_node = new_child_and_node[1]
198
+ end
199
+ end"
200
+ end.join("\n")
201
+ else
202
+ ""
203
+ end
204
+ }
205
+
206
+ if new_node.equal?(node)
207
+ [node, parent]
208
+ else
209
+ [new_node, parent]
210
+ end
211
+ end
212
+ end
213
+
214
+ def #{node_method}_with_modifications(node, parent)
215
+ new_node_and_new_parent = #{node_method}(node, parent)
216
+ apply_modifications(node, parent, new_node_and_new_parent)
217
+ end
218
+ RUBY
219
+ end
220
+
219
221
  [
220
222
  Language::Nodes::Argument,
221
223
  Language::Nodes::Directive,
@@ -256,6 +258,8 @@ module GraphQL
256
258
  make_visit_methods(ast_node_class)
257
259
  end
258
260
 
261
+ # rubocop:enable Development/NoEvalCop
262
+
259
263
  private
260
264
 
261
265
  def apply_modifications(node, parent, new_node_and_new_parent)