Class: GraphQL::Language::Visitor

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/language/visitor.rb

Overview

Depth-first traversal through the tree, calling hooks at each stop.

Examples:

Create a visitor counting certain field names

class NameCounter < GraphQL::Language::Visitor
  def initialize(document, field_name)
    super(document)
    @field_name = field_name
    @count = 0
  end

  attr_reader :count

  def on_field(node, parent)
    # if this field matches our search, increment the counter
    if node.name == @field_name
      @count += 1
    end
    # Continue visiting subfields:
    super
  end
end

# Initialize a visitor
visitor = NameCounter.new(document, "name")
# Run it
visitor.visit
# Check the result
visitor.count
# => 3

Defined Under Namespace

Classes: DeleteNode, NodeVisitor

Constant Summary collapse

SKIP =
Deprecated.

Use super to continue the visit; or don’t call it to halt.

If any hook returns this value, the GraphQL::Language::Visitor stops visiting this node right away

:_skip
DELETE_NODE =

When this is returned from a visitor method, Then the node passed into the method is removed from parent’s children.

DeleteNode.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document) ⇒ Visitor

Returns a new instance of Visitor.



45
46
47
48
49
# File 'lib/graphql/language/visitor.rb', line 45

def initialize(document)
  @document = document
  @visitors = {}
  @result = nil
end

Instance Attribute Details

#resultGraphQL::Language::Nodes::Document (readonly)

Returns The document with any modifications applied.

Returns:



52
53
54
# File 'lib/graphql/language/visitor.rb', line 52

def result
  @result
end

Class Method Details

.make_visit_methods(ast_node_class) ⇒ Object

We don’t use alias here because it breaks super



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/graphql/language/visitor.rb', line 80

def self.make_visit_methods(ast_node_class)
  node_method = ast_node_class.visit_method
  children_of_type = ast_node_class.children_of_type
  child_visit_method = :"#{node_method}_children"

  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    # The default implementation for visiting an AST node.
    # It doesn't _do_ anything, but it continues to visiting the node's children.
    # To customize this hook, override one of its make_visit_methods (or the base method?)
    # in your subclasses.
    #
    # For compatibility, it calls hook procs, too.
    # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    def #{node_method}(node, parent)
      if node.equal?(DELETE_NODE)
        # This might be passed to `super(DELETE_NODE, ...)`
        # by a user hook, don't want to keep visiting in that case.
        [node, parent]
      else
        # Run hooks if there are any
        new_node = node
        no_hooks = !@visitors.key?(node.class)
        if no_hooks || begin_visit(new_node, parent)
          #{
            if method_defined?(child_visit_method)
              "new_node = #{child_visit_method}(new_node)"
            elsif children_of_type
              children_of_type.map do |child_accessor, child_class|
                "node.#{child_accessor}.each do |child_node|
                  new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
                  # Reassign `node` in case the child hook makes a modification
                  if new_child_and_node.is_a?(Array)
                    new_node = new_child_and_node[1]
                  end
                end"
              end.join("\n")
            else
              ""
            end
          }
        end
        end_visit(new_node, parent) unless no_hooks

        if new_node.equal?(node)
          [node, parent]
        else
          [new_node, parent]
        end
      end
    end

    def #{node_method}_with_modifications(node, parent)
      new_node_and_new_parent = #{node_method}(node, parent)
      apply_modifications(node, parent, new_node_and_new_parent)
    end
  RUBY
end

Instance Method Details

#[](node_class) ⇒ NodeVisitor

Deprecated.

see on_ methods, like #on_field

Get a NodeVisitor for node_class

Examples:

Run a hook whenever you enter a new Field

visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { p "Here's a field" }

Parameters:

  • node_class (Class)

    The node class that you want to listen to

Returns:



61
62
63
# File 'lib/graphql/language/visitor.rb', line 61

def [](node_class)
  @visitors[node_class] ||= NodeVisitor.new
end

#on_argument_children(new_node) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/graphql/language/visitor.rb', line 218

def on_argument_children(new_node)
  new_node.children.each do |value_node|
    new_child_and_node = case value_node
    when Language::Nodes::VariableIdentifier
      on_variable_identifier_with_modifications(value_node, new_node)
    when Language::Nodes::InputObject
      on_input_object_with_modifications(value_node, new_node)
    when Language::Nodes::Enum
      on_enum_with_modifications(value_node, new_node)
    when Language::Nodes::NullValue
      on_null_value_with_modifications(value_node, new_node)
    else
      raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})"
    end
    # Reassign `node` in case the child hook makes a modification
    if new_child_and_node.is_a?(Array)
      new_node = new_child_and_node[1]
    end
  end
  new_node
end

#on_document_children(document_node) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/graphql/language/visitor.rb', line 140

def on_document_children(document_node)
  new_node = document_node
  document_node.children.each do |child_node|
    visit_method = :"#{child_node.visit_method}_with_modifications"
    new_child_and_node = public_send(visit_method, child_node, new_node)
    # Reassign `node` in case the child hook makes a modification
    if new_child_and_node.is_a?(Array)
      new_node = new_child_and_node[1]
    end
  end
  new_node
end

#on_field_children(new_node) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/graphql/language/visitor.rb', line 153

def on_field_children(new_node)
  new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop
    new_child_and_node = on_argument_with_modifications(arg_node, new_node)
    # Reassign `node` in case the child hook makes a modification
    if new_child_and_node.is_a?(Array)
      new_node = new_child_and_node[1]
    end
  end
  new_node = visit_directives(new_node)
  new_node = visit_selections(new_node)
  new_node
end

#on_fragment_definition_children(new_node) ⇒ Object Also known as: on_inline_fragment_children



197
198
199
200
201
# File 'lib/graphql/language/visitor.rb', line 197

def on_fragment_definition_children(new_node)
  new_node = visit_directives(new_node)
  new_node = visit_selections(new_node)
  new_node
end

#on_operation_definition_children(new_node) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/graphql/language/visitor.rb', line 205

def on_operation_definition_children(new_node)
  new_node.variables.each do |arg_node|
    new_child_and_node = on_variable_definition_with_modifications(arg_node, new_node)
    # Reassign `node` in case the child hook makes a modification
    if new_child_and_node.is_a?(Array)
      new_node = new_child_and_node[1]
    end
  end
  new_node = visit_directives(new_node)
  new_node = visit_selections(new_node)
  new_node
end

#visitvoid

This method returns an undefined value.

Visit document and all children, applying hooks as you go



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/graphql/language/visitor.rb', line 67

def visit
  # `@document` may be any kind of node:
  visit_method = :"#{@document.visit_method}_with_modifications"
  result = public_send(visit_method, @document, nil)
  @result = if result.is_a?(Array)
    result.first
  else
    # The node wasn't modified
    @document
  end
end

#visit_directives(new_node) ⇒ Object



166
167
168
169
170
171
172
173
174
175
# File 'lib/graphql/language/visitor.rb', line 166

def visit_directives(new_node)
  new_node.directives.each do |dir_node|
    new_child_and_node = on_directive_with_modifications(dir_node, new_node)
    # Reassign `node` in case the child hook makes a modification
    if new_child_and_node.is_a?(Array)
      new_node = new_child_and_node[1]
    end
  end
  new_node
end

#visit_selections(new_node) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/graphql/language/visitor.rb', line 177

def visit_selections(new_node)
  new_node.selections.each do |selection|
    new_child_and_node = case selection
    when GraphQL::Language::Nodes::Field
      on_field_with_modifications(selection, new_node)
    when GraphQL::Language::Nodes::InlineFragment
      on_inline_fragment_with_modifications(selection, new_node)
    when GraphQL::Language::Nodes::FragmentSpread
      on_fragment_spread_with_modifications(selection, new_node)
    else
      raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})"
    end
    # Reassign `node` in case the child hook makes a modification
    if new_child_and_node.is_a?(Array)
      new_node = new_child_and_node[1]
    end
  end
  new_node
end