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
@return [GraphQL::Language::Nodes::Document] The document with any modifications applied
Public Class Methods
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
# File lib/graphql/language/visitor.rb, line 45 def initialize(document) @document = document @visitors = {} @result = nil end
Private Class Methods
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
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
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 ‘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
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
# 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
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
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