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

See Also:

Defined Under Namespace

Classes: DeleteNode

Constant Summary collapse

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.



42
43
44
45
# File 'lib/graphql/language/visitor.rb', line 42

def initialize(document)
  @document = document
  @result = nil
end

Instance Attribute Details

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

Returns The document with any modifications applied.

Returns:



48
49
50
# File 'lib/graphql/language/visitor.rb', line 48

def result
  @result
end

Class Method Details

.make_visit_methods(ast_node_class) ⇒ Object

We don't use alias here because it breaks super



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
# File 'lib/graphql/language/visitor.rb', line 65

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.
    #
    # @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
        new_node = node
        #{
          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
        }

        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

#on_argument_children(new_node) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/graphql/language/visitor.rb', line 197

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



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/graphql/language/visitor.rb', line 119

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



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/graphql/language/visitor.rb', line 132

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



176
177
178
179
180
# File 'lib/graphql/language/visitor.rb', line 176

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



184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/graphql/language/visitor.rb', line 184

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



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/graphql/language/visitor.rb', line 52

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



145
146
147
148
149
150
151
152
153
154
# File 'lib/graphql/language/visitor.rb', line 145

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



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/graphql/language/visitor.rb', line 156

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