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

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.



40
41
42
43
# File 'lib/graphql/language/visitor.rb', line 40

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

Instance Attribute Details

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

Returns The document with any modifications applied.

Returns:



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

def result
  @result
end

Class Method Details

.make_visit_methods(ast_node_class) ⇒ Object

We don’t use alias here because it breaks super



63
64
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
# File 'lib/graphql/language/visitor.rb', line 63

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



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

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



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

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



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

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



174
175
176
177
178
# File 'lib/graphql/language/visitor.rb', line 174

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



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

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



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

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



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

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



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

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