class GraphQL::Analysis::AST::Visitor

Depth first traversal through a query AST, calling AST analyzers along the way.

The visitor is a special case of GraphQL::Language::Visitor, visiting only the selected operation, providing helpers for common use cases such as skipped fields and visiting fragment spreads.

@see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries

Attributes

object_types[R]

@return [Array<GraphQL::ObjectType>] Types whose scope we've entered

query[R]

@return [GraphQL::Query] the query being visited

rescued_errors[R]

@return [Array<GraphQL::AnalysisError]

Public Class Methods

new(query:, analyzers:) click to toggle source
Calls superclass method GraphQL::Language::Visitor::new
# File lib/graphql/analysis/ast/visitor.rb, line 14
def initialize(query:, analyzers:)
  @analyzers = analyzers
  @path = []
  @object_types = []
  @directives = []
  @field_definitions = []
  @argument_definitions = []
  @directive_definitions = []
  @rescued_errors = []
  @query = query
  @schema = query.schema
  @response_path = []
  @skip_stack = [false]
  super(query.selected_operation)
end

Public Instance Methods

argument_definition() click to toggle source

@return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one

# File lib/graphql/analysis/ast/visitor.rb, line 205
def argument_definition
  @argument_definitions.last
end
arguments_for(ast_node, field_definition) click to toggle source

@return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables @see {GraphQL::Query#arguments_for}

# File lib/graphql/analysis/ast/visitor.rb, line 48
def arguments_for(ast_node, field_definition)
  @query.arguments_for(ast_node, field_definition)
end
directive_definition() click to toggle source

@return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one

# File lib/graphql/analysis/ast/visitor.rb, line 200
def directive_definition
  @directive_definitions.last
end
field_definition() click to toggle source

@return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one

# File lib/graphql/analysis/ast/visitor.rb, line 190
def field_definition
  @field_definitions.last
end
on_abstract_node(node, parent) click to toggle source
# File lib/graphql/analysis/ast/visitor.rb, line 173
def on_abstract_node(node, parent)
  call_analyzers(:on_enter_abstract_node, node, parent)
  super
  call_analyzers(:on_leave_abstract_node, node, parent)
end
on_argument(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 138
def on_argument(node, parent)
  argument_defn = if (arg = @argument_definitions.last)
    arg_type = arg.type.unwrap
    if arg_type.kind.input_object?
      arg_type.get_argument(node.name, @query.context)
    else
      nil
    end
  elsif (directive_defn = @directive_definitions.last)
    directive_defn.get_argument(node.name, @query.context)
  elsif (field_defn = @field_definitions.last)
    field_defn.get_argument(node.name, @query.context)
  else
    nil
  end

  @argument_definitions.push(argument_defn)
  @path.push(node.name)
  call_analyzers(:on_enter_argument, node, parent)
  super
  call_analyzers(:on_leave_argument, node, parent)
  @argument_definitions.pop
  @path.pop
end
on_directive(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 129
def on_directive(node, parent)
  directive_defn = @schema.directives[node.name]
  @directive_definitions.push(directive_defn)
  call_analyzers(:on_enter_directive, node, parent)
  super
  call_analyzers(:on_leave_directive, node, parent)
  @directive_definitions.pop
end
on_field(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 100
def on_field(node, parent)
  @response_path.push(node.alias || node.name)
  parent_type = @object_types.last
  # This could be nil if the previous field wasn't found:
  field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
  @field_definitions.push(field_definition)
  if !field_definition.nil?
    next_object_type = field_definition.type.unwrap
    @object_types.push(next_object_type)
  else
    @object_types.push(nil)
  end
  @path.push(node.alias || node.name)

  @skipping = @skip_stack.last || skip?(node)
  @skip_stack << @skipping

  call_analyzers(:on_enter_field, node, parent)
  super

  @skipping = @skip_stack.pop

  call_analyzers(:on_leave_field, node, parent)
  @response_path.pop
  @field_definitions.pop
  @object_types.pop
  @path.pop
end
on_fragment_definition(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 80
def on_fragment_definition(node, parent)
  on_fragment_with_type(node) do
    @path.push("fragment #{node.name}")
    @in_fragment_def = false
    call_analyzers(:on_enter_fragment_definition, node, parent)
    super
    @in_fragment_def = false
    call_analyzers(:on_leave_fragment_definition, node, parent)
  end
end
on_fragment_spread(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 163
def on_fragment_spread(node, parent)
  @path.push("... #{node.name}")
  call_analyzers(:on_enter_fragment_spread, node, parent)
  enter_fragment_spread_inline(node)
  super
  leave_fragment_spread_inline(node)
  call_analyzers(:on_leave_fragment_spread, node, parent)
  @path.pop
end
on_inline_fragment(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 91
def on_inline_fragment(node, parent)
  on_fragment_with_type(node) do
    @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
    call_analyzers(:on_enter_inline_fragment, node, parent)
    super
    call_analyzers(:on_leave_inline_fragment, node, parent)
  end
end
on_operation_definition(node, parent) click to toggle source

Visitor Hooks

Calls superclass method
# File lib/graphql/analysis/ast/visitor.rb, line 69
def on_operation_definition(node, parent)
  object_type = @schema.root_type_for_operation(node.operation_type)
  @object_types.push(object_type)
  @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
  call_analyzers(:on_enter_operation_definition, node, parent)
  super
  call_analyzers(:on_leave_operation_definition, node, parent)
  @object_types.pop
  @path.pop
end
parent_type_definition() click to toggle source

@return [GraphQL::BaseType] The type which the current type came from

# File lib/graphql/analysis/ast/visitor.rb, line 185
def parent_type_definition
  @object_types[-2]
end
previous_argument_definition() click to toggle source

@return [GraphQL::Argument, nil] The previous GraphQL argument

# File lib/graphql/analysis/ast/visitor.rb, line 210
def previous_argument_definition
  @argument_definitions[-2]
end
previous_field_definition() click to toggle source

@return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to

# File lib/graphql/analysis/ast/visitor.rb, line 195
def previous_field_definition
  @field_definitions[-2]
end
response_path() click to toggle source

@return [Array<String>] The path to the response key for the current field

# File lib/graphql/analysis/ast/visitor.rb, line 63
def response_path
  @response_path.dup
end
skipping?() click to toggle source

@return [Boolean] If the current node should be skipped because of a skip or include directive

# File lib/graphql/analysis/ast/visitor.rb, line 58
def skipping?
  @skipping
end
type_definition() click to toggle source

@return [GraphQL::BaseType] The current object type

# File lib/graphql/analysis/ast/visitor.rb, line 180
def type_definition
  @object_types.last
end
visit() click to toggle source
Calls superclass method GraphQL::Language::Visitor#visit
# File lib/graphql/analysis/ast/visitor.rb, line 39
def visit
  return unless @document
  super
end
visiting_fragment_definition?() click to toggle source

@return [Boolean] If the visitor is currently inside a fragment definition

# File lib/graphql/analysis/ast/visitor.rb, line 53
def visiting_fragment_definition?
  @in_fragment_def
end

Private Instance Methods

call_analyzers(method, node, parent) click to toggle source
# File lib/graphql/analysis/ast/visitor.rb, line 245
def call_analyzers(method, node, parent)
  @analyzers.each do |analyzer|
    begin
      analyzer.public_send(method, node, parent, self)
    rescue AnalysisError => err
      @rescued_errors << err
    end
  end
end
enter_fragment_spread_inline(fragment_spread) click to toggle source

Visit a fragment spread inline instead of visiting the definition by itself.

# File lib/graphql/analysis/ast/visitor.rb, line 218
def enter_fragment_spread_inline(fragment_spread)
  fragment_def = query.fragments[fragment_spread.name]

  object_type = if fragment_def.type
    @query.warden.get_type(fragment_def.type.name)
  else
    object_types.last
  end

  object_types << object_type

  fragment_def.selections.each do |selection|
    visit_node(selection, fragment_def)
  end
end
leave_fragment_spread_inline(_fragment_spread) click to toggle source

Visit a fragment spread inline instead of visiting the definition by itself.

# File lib/graphql/analysis/ast/visitor.rb, line 236
def leave_fragment_spread_inline(_fragment_spread)
  object_types.pop
end
on_fragment_with_type(node) { |node| ... } click to toggle source
# File lib/graphql/analysis/ast/visitor.rb, line 255
def on_fragment_with_type(node)
  object_type = if node.type
    @query.warden.get_type(node.type.name)
  else
    @object_types.last
  end
  @object_types.push(object_type)
  yield(node)
  @object_types.pop
  @path.pop
end
skip?(ast_node) click to toggle source
# File lib/graphql/analysis/ast/visitor.rb, line 240
def skip?(ast_node)
  dir = ast_node.directives
  dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
end