class GraphQL::Language::Visitor

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

@example 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

Constants

DELETE_NODE

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

SKIP

If any hook returns this value, the {Visitor} stops visiting this node right away @deprecated Use ‘super` to continue the visit; or don’t call it to halt.

Attributes

result[R]

@return [GraphQL::Language::Nodes::Document] The document with any modifications applied

Public Class Methods

make_visit_method(node_method) click to toggle source

We don’t use ‘alias` here because it breaks `super`

# File lib/graphql/language/visitor.rb, line 120
      def self.make_visit_method(node_method)
        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
          def #{node_method}(node, parent)
            child_mod = on_abstract_node(node, parent)
            # If visiting the children returned changes, continue passing those.
            child_mod || [node, parent]
          end
        RUBY
      end
new(document) click to toggle source
# File lib/graphql/language/visitor.rb, line 45
def initialize(document)
  @document = document
  @visitors = {}
  @result = nil
end

Private Class Methods

apply_hooks(hooks, node, parent) click to toggle source

If one of the visitors returns SKIP, stop visiting this node

# File lib/graphql/language/visitor.rb, line 213
def self.apply_hooks(hooks, node, parent)
  hooks.each do |proc|
    return false if proc.call(node, parent) == SKIP
  end
  true
end

Public Instance Methods

[](node_class) click to toggle source

Get a {NodeVisitor} for ‘node_class` @param node_class [Class] The node class that you want to listen to @return [NodeVisitor]

@example Run a hook whenever you enter a new Field

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

@deprecated see ‘on_` methods, like {#on_field}

# File lib/graphql/language/visitor.rb, line 61
def [](node_class)
  @visitors[node_class] ||= NodeVisitor.new
end
on_abstract_node(node, parent) click to toggle source

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_methodes (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`.

# File lib/graphql/language/visitor.rb, line 91
def on_abstract_node(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.
    nil
  else
    # Run hooks if there are any
    new_node = node
    no_hooks = !@visitors.key?(node.class)
    if no_hooks || begin_visit(new_node, parent)
      node.children.each do |child_node|
        new_child_and_node = on_node_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
    end_visit(new_node, parent) unless no_hooks

    if new_node.equal?(node)
      nil
    else
      [new_node, parent]
    end
  end
end
visit() click to toggle source

Visit ‘document` and all children, applying hooks as you go @return [void]

# File lib/graphql/language/visitor.rb, line 67
def visit
  result = on_node_with_modifications(@document, nil)
  @result = if result.is_a?(Array)
    result.first
  else
    # The node wasn't modified
    @document
  end
end
visit_node(node, parent) click to toggle source

Call the user-defined handler for ‘node`.

# File lib/graphql/language/visitor.rb, line 78
def visit_node(node, parent)
  public_send(node.visit_method, node, parent)
end

Private Instance Methods

begin_visit(node, parent) click to toggle source
# File lib/graphql/language/visitor.rb, line 201
def begin_visit(node, parent)
  node_visitor = self[node.class]
  self.class.apply_hooks(node_visitor.enter, node, parent)
end
end_visit(node, parent) click to toggle source

Should global ‘leave` visitors come first or last?

# File lib/graphql/language/visitor.rb, line 207
def end_visit(node, parent)
  node_visitor = self[node.class]
  self.class.apply_hooks(node_visitor.leave, node, parent)
end
on_node_with_modifications(node, parent) click to toggle source

Run the hooks for ‘node`, and if the hooks return a copy of `node`, copy `parent` so that it contains the copy of that node as a child, then return the copies If a non-array value is returned, consuming functions should ignore said value

# File lib/graphql/language/visitor.rb, line 173
def on_node_with_modifications(node, parent)
  new_node_and_new_parent = visit_node(node, parent)
  if new_node_and_new_parent.is_a?(Array)
    new_node = new_node_and_new_parent[0]
    new_parent = new_node_and_new_parent[1]
    if new_node.is_a?(Nodes::AbstractNode) && !node.equal?(new_node)
      # The user-provided hook returned a new node.
      new_parent = new_parent && new_parent.replace_child(node, new_node)
      return new_node, new_parent
    elsif new_node.equal?(DELETE_NODE)
      # The user-provided hook requested to remove this node
      new_parent = new_parent && new_parent.delete_child(node)
      return nil, new_parent
    elsif new_node_and_new_parent.none? { |n| n == nil || n.class < Nodes::AbstractNode }
      # The user-provided hook returned an array of who-knows-what
      # return nil here to signify that no changes should be made
      nil
    else
      new_node_and_new_parent
    end
  else
    # The user-provided hook didn't make any modifications.
    # In fact, the hook might have returned who-knows-what, so
    # ignore the return value and use the original values.
    new_node_and_new_parent
  end
end